Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environments tolerate concise configuration #649

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ And the `checkrun` page will look like this:
<img width="860" alt="image" src="https://github.com/github/safe-settings/assets/57544838/893ff4e6-904c-4a07-924a-7c23dc068983">
</p>

### The Settings File
### The Settings Files

The settings file can be used to set the policies at the `org`, `suborg` or `repo` level.
The settings files can be used to set the policies at the `org`, `suborg` or `repo` level.

The following can be configured:

Expand All @@ -284,6 +284,7 @@ The following can be configured:
- `Autolinks`
- `Repository name validation` using regex pattern
- `Rulesets`
- `Environments` - wait timer, required reviewers, prevent self review, protected branches deployment branch policy, custom deployment branch policy, variables, deployment protection rules

It is possible to provide an `include` or `exclude` settings to restrict the `collaborators`, `teams`, `labels` to a list of repos or exclude a set of repos for a collaborator.

Expand Down
12 changes: 12 additions & 0 deletions app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ default_permissions:
repository_custom_properties: write
organization_custom_properties: admin

# Workflows, workflow runs and artifacts. (needed to read environments when repo is private or internal)
# https://developer.github.com/v3/apps/permissions/#repository-permissions-for-actions
actions: read

# Repository creation, deletion, settings, teams, and collaborators.
# https://developer.github.com/v3/apps/permissions/#permission-on-administration
administration: write
Expand All @@ -50,6 +54,10 @@ default_permissions:
# https://developer.github.com/v3/apps/permissions/#permission-on-deployments
# deployments: read

# Manage repository environments.
# https://developer.github.com/v3/apps/permissions/#repository-permissions-for-environments
environments: write

# Issues and related comments, assignees, labels, and milestones.
# https://developer.github.com/v3/apps/permissions/#permission-on-issues
issues: write
Expand Down Expand Up @@ -106,6 +114,10 @@ default_permissions:
# https://developer.github.com/v3/apps/permissions/
organization_administration: write

# Manage Actions repository variables.
# https://developer.github.com/v3/apps/permissions/#repository-permissions-for-variables
variables: write


# The name of the GitHub App. Defaults to the name specified in package.json
name: Safe Settings
Expand Down
3 changes: 3 additions & 0 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,17 @@ Every deployment will need an [App](https://developer.github.com/apps/).

#### Repository Permissions

- Actions: **Read-only**
- Administration: **Read & Write**
- Checks: **Read & Write**
- Commit statuses: **Read & Write**
- Contents: **Read & Write**
- Custom properties: **Read & Write**
- Environments: **Read & Write**
- Issues: **Read & Write**
- Metadata: **Read-only**
- Pull requests: **Read & Write**
- Variables: **Read & Write**

#### Organization Permissions

Expand Down
136 changes: 114 additions & 22 deletions lib/plugins/environments.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const Diffable = require('./diffable')
const MergeDeep = require('../mergeDeep')
const NopCommand = require('../nopcommand')

module.exports = class Environments extends Diffable {
constructor(...args) {
Expand All @@ -14,7 +16,11 @@ module.exports = class Environments extends Diffable {
});
}
})
}
}

// Remove 'name' from filtering list so Environments with only a name defined are processed.
MergeDeep.NAME_FIELDS.splice(MergeDeep.NAME_FIELDS.indexOf('name'), 1)

}

async find() {
Expand Down Expand Up @@ -78,7 +84,7 @@ module.exports = class Environments extends Diffable {
const wait_timer = existing.wait_timer !== attrs.wait_timer;
const prevent_self_review = existing.prevent_self_review !== attrs.prevent_self_review;
const reviewers = JSON.stringify(existing.reviewers.sort((x1, x2) => x1.id - x2.id)) !== JSON.stringify(attrs.reviewers.sort((x1, x2) => x1.id - x2.id));

let existing_custom_branch_policies = existing.deployment_branch_policy === null ? null : existing.deployment_branch_policy.custom_branch_policies;
if(typeof(existing_custom_branch_policies) === 'object' && existing_custom_branch_policies !== null) {
existing_custom_branch_policies = existing_custom_branch_policies.sort();
Expand Down Expand Up @@ -158,6 +164,7 @@ module.exports = class Environments extends Diffable {

if(variables) {
let existingVariables = [...existing.variables];

for(let variable of attrs.variables) {
const existingVariable = existingVariables.find((_var) => _var.name === variable.name);
if(existingVariable) {
Expand Down Expand Up @@ -195,6 +202,7 @@ module.exports = class Environments extends Diffable {

if(deployment_protection_rules) {
let existingRules = [...existing.deployment_protection_rules];

for(let rule of attrs.deployment_protection_rules) {
const existingRule = existingRules.find((_rule) => _rule.id === rule.id);

Expand Down Expand Up @@ -227,13 +235,14 @@ module.exports = class Environments extends Diffable {
wait_timer: attrs.wait_timer,
prevent_self_review: attrs.prevent_self_review,
reviewers: attrs.reviewers,
deployment_branch_policy: attrs.deployment_branch_policy === null ? null : {
protected_branches: attrs.deployment_branch_policy.protected_branches,
deployment_branch_policy: attrs.deployment_branch_policy == null ? null : {
protected_branches: !!attrs.deployment_branch_policy.protected_branches,
custom_branch_policies: !!attrs.deployment_branch_policy.custom_branch_policies
}
});

if(attrs.deployment_branch_policy && attrs.deployment_branch_policy.custom_branch_policies) {

for(let policy of attrs.deployment_branch_policy.custom_branch_policies) {
await this.github.request('POST /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
org: this.repo.owner,
Expand All @@ -242,26 +251,34 @@ module.exports = class Environments extends Diffable {
name: policy.name
});
}
}


for(let variable of attrs.variables) {
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/variables`, {
org: this.repo.owner,
repo: this.repo.repo,
environment_name: attrs.name,
name: variable.name,
value: variable.value
});
}

for(let rule of attrs.deployment_protection_rules) {
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/deployment_protection_rules`, {
org: this.repo.owner,
repo: this.repo.repo,
environment_name: attrs.name,
integration_id: rule.app_id
});
if(attrs.variables) {

for(let variable of attrs.variables) {
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/variables`, {
org: this.repo.owner,
repo: this.repo.repo,
environment_name: attrs.name,
name: variable.name,
value: variable.value
});
}

}

if(attrs.deployment_protection_rules) {

for(let rule of attrs.deployment_protection_rules) {
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/deployment_protection_rules`, {
org: this.repo.owner,
repo: this.repo.repo,
environment_name: attrs.name,
integration_id: rule.app_id
});
}

}
}

Expand All @@ -272,4 +289,79 @@ module.exports = class Environments extends Diffable {
environment_name: existing.name
});
}
}

sync () {
const resArray = []
if (this.entries) {
let filteredEntries = this.filterEntries()
return this.find().then(existingRecords => {

// Filter out all empty entries (usually from repo override)
for (const entry of filteredEntries) {
for (const key of Object.keys(entry)) {
if (entry[key] === null || entry[key] === undefined) {
delete entry[key]
}
}
}
filteredEntries = filteredEntries.filter(entry => Object.keys(entry).filter(key => !MergeDeep.NAME_FIELDS.includes(key)).length !== 0)

const changes = []

existingRecords.forEach(x => {
if (!filteredEntries.find(y => this.comparator(x, y))) {
const change = this.remove(x).then(res => {
if (this.nop) {
return resArray.push(res)
}
return res
})
changes.push(change)
}
})

filteredEntries.forEach(attrs => {
const existing = existingRecords.find(record => {
return this.comparator(record, attrs)
})

if (!existing) {
const change = this.add(attrs).then(res => {
if (this.nop) {
return resArray.push(res)
}
return res
})
changes.push(change)
} else if (this.changed(existing, attrs)) {
const change = this.update(existing, attrs).then(res => {
if (this.nop) {
return resArray.push(res)
}
return res
})
changes.push(change)
}
})

if (this.nop) {
return Promise.resolve(resArray)
}
return Promise.all(changes)
}).catch(e => {
if (this.nop) {
if (e.status === 404) {
// Ignore 404s which can happen in dry-run as the repo may not exist.
return Promise.resolve(resArray)
} else {
resArray.push(new NopCommand(this.constructor.name, this.repo, null, `error ${e} in ${this.constructor.name} for repo: ${JSON.stringify(this.repo)} entries ${JSON.stringify(this.entries)}`, 'ERROR'))
return Promise.resolve(resArray)
}
} else {
this.logError(`Error ${e} in ${this.constructor.name} for repo: ${JSON.stringify(this.repo)} entries ${JSON.stringify(this.entries)}`)
}
})
}
}

}
Loading