Skip to content

Commit

Permalink
feat: Add support for environments (#671)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Travi <github@travi.org>
Fixes #607
  • Loading branch information
rlmartin committed Feb 2, 2023
1 parent 97c12a3 commit 0ca994f
Show file tree
Hide file tree
Showing 7 changed files with 719 additions and 1 deletion.
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -116,6 +116,25 @@ collaborators:
# * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions.
# * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access.

# See https://docs.github.com/en/rest/deployments/environments#create-or-update-an-environment for available options
# Note: deployment_branch_policy differs from the API for ease of use. Either protected_branches (boolean) OR custom_branches (array of strings) can be provided; this will manage the API requirements under the hood. See https://docs.github.com/en/rest/deployments/branch-policies for documentation of custom_branches. If both are provided in an unexpected manner, protected_branches will be used.
# Either removing or simply not setting deployment_branch_policy will restore the default 'All branches' setting.
environments:
- name: production
wait_timer: 5
reviewers:
- id: 1
type: 'Team'
- id: 2
type: 'User'
deployment_branch_policy:
protected_branches: true
- name: development
deployment_branch_policy:
custom_branches:
- main
- dev/*

# See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options
teams:
- name: core
Expand Down
180 changes: 180 additions & 0 deletions lib/plugins/environments.js
@@ -0,0 +1,180 @@
const Diffable = require('./diffable')

const environmentRepoEndpoint = '/repos/:org/:repo/environments/:environment_name'

module.exports = class Environments extends Diffable {
constructor (...args) {
super(...args)

if (this.entries) {
// Force all names to lowercase to avoid comparison issues.
this.entries.forEach(environment => {
environment.name = environment.name.toLowerCase()
})
}
}

async find () {
const {
data: { environments }
} = await this.github.request('GET /repos/:org/:repo/environments', {
org: this.repo.owner,
repo: this.repo.repo
})
return Promise.all(
environments.map(async environment => {
if (environment.deployment_branch_policy) {
if (environment.deployment_branch_policy.custom_branch_policies) {
const branchPolicies = await this.getDeploymentBranchPolicies(
this.repo.owner,
this.repo.repo,
environment.name
)
environment.deployment_branch_policy = {
custom_branches: branchPolicies.map(_ => _.name)
}
} else {
environment.deployment_branch_policy = {
protected_branches: true
}
}
}
return {
...environment,
// Force all names to lowercase to avoid comparison issues.
name: environment.name.toLowerCase()
}
})
)
}

comparator (existing, attrs) {
return existing.name === attrs.name
}

changed (existing, attrs) {
if (!attrs.wait_timer) attrs.wait_timer = 0
return (
(existing.wait_timer || 0) !== attrs.wait_timer ||
this.reviewersToString(existing.reviewers) !== this.reviewersToString(attrs.reviewers) ||
this.deploymentBranchPolicyToString(existing.deployment_branch_policy) !==
this.deploymentBranchPolicyToString(attrs.deployment_branch_policy)
)
}

async update (existing, attrs) {
if (existing.deployment_branch_policy && existing.deployment_branch_policy.custom_branches) {
const branchPolicies = await this.getDeploymentBranchPolicies(this.repo.owner, this.repo.repo, existing.name)
await Promise.all(
branchPolicies.map(branchPolicy =>
this.github.request(
'DELETE /repos/:org/:repo/environments/:environment_name/deployment-branch-policies/:id',
{
org: this.repo.owner,
repo: this.repo.repo,
environment_name: existing.name,
id: branchPolicy.id
}
)
)
)
}
return this.add(attrs)
}

async add (attrs) {
await this.github.request(`PUT ${environmentRepoEndpoint}`, this.toParams({ name: attrs.name }, attrs))
if (attrs.deployment_branch_policy && attrs.deployment_branch_policy.custom_branches) {
await Promise.all(
attrs.deployment_branch_policy.custom_branches.map(name =>
this.github.request(`POST /repos/:org/:repo/environments/:environment_name/deployment-branch-policies`, {
org: this.repo.owner,
repo: this.repo.repo,
environment_name: attrs.name,
name
})
)
)
}
}

remove (existing) {
return this.github.request(`DELETE ${environmentRepoEndpoint}`, {
environment_name: existing.name,
repo: this.repo.repo,
org: this.repo.owner
})
}

reviewersToString (attrs) {
if (attrs === null || attrs === undefined) {
return ''
} else {
attrs.sort((a, b) => {
if (a.id < b.id) return -1
if (a.id > b.id) return 1
if (a.type < b.type) return -1
if (a.type > b.type) return 1
return 0
})
return JSON.stringify(
attrs.map(reviewer => {
return {
id: reviewer.id,
type: reviewer.type
}
})
)
}
}

deploymentBranchPolicyToString (attrs) {
if (attrs === null || attrs === undefined) {
return ''
} else {
return JSON.stringify(
this.shouldUseProtectedBranches(attrs.protected_branches, attrs.custom_branches)
? { protected_branches: true }
: { custom_branches: attrs.custom_branches.sort() }
)
}
}

async getDeploymentBranchPolicies (owner, repo, environmentName) {
const {
data: { branch_policies: branchPolicies }
} = await this.github.request('GET /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
org: owner,
repo,
environment_name: environmentName
})
return branchPolicies
}

toParams (existing, attrs) {
const deploymentBranchPolicy = attrs.deployment_branch_policy
? this.shouldUseProtectedBranches(
attrs.deployment_branch_policy.protected_branches,
attrs.deployment_branch_policy.custom_branches
)
? { protected_branches: true, custom_branch_policies: false }
: { protected_branches: false, custom_branch_policies: true }
: null
return {
environment_name: existing.name,
repo: this.repo.repo,
org: this.repo.owner,
wait_timer: attrs.wait_timer,
reviewers: attrs.reviewers,
deployment_branch_policy: deploymentBranchPolicy
}
}

shouldUseProtectedBranches (protectedBranches, customBranchPolicies) {
if (protectedBranches || customBranchPolicies === undefined || customBranchPolicies === null) {
return true // Returning booleans like this to avoid unexpected datatypes that result in truthy values
} else {
return false
}
}
}
1 change: 1 addition & 0 deletions lib/settings.js
Expand Up @@ -38,6 +38,7 @@ Settings.PLUGINS = {
repository: require('./plugins/repository'),
labels: require('./plugins/labels'),
collaborators: require('./plugins/collaborators'),
environments: require('./plugins/environments'),
teams: require('./plugins/teams'),
milestones: require('./plugins/milestones'),
branches: require('./plugins/branches')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -16,7 +16,7 @@
"lint:peer": "npm ls >/dev/null",
"test:unit": "jest 'test/unit/'",
"test:unit:watch": "npm run test:unit -- --watch",
"test:integration": "jest --test-timeout=10000 'test/integration/'",
"test:integration": "jest --test-timeout=20000 'test/integration/'",
"test:integration:debug": "LOG_LEVEL=debug DEBUG=nock.* run-s test:integration"
},
"author": "Brandon Keepers",
Expand Down
81 changes: 81 additions & 0 deletions test/fixtures/environments-config.yml
@@ -0,0 +1,81 @@
environments:
- name: changed-wait-timer
wait_timer: 10

- name: changed-reviewers-type
reviewers:
- id: 1
type: User
- id: 2
type: User

- name: changed-reviewers-id
reviewers:
- id: 1
type: Team
- id: 3
type: User

- name: changed-reviewers-remove
reviewers:
- id: 1
type: Team

- name: changed-reviewers-add
reviewers:
- id: 1
type: Team
- id: 2
type: User
- id: 3
type: User

- name: changed-deployment-branch-policy
deployment_branch_policy:
custom_branches:
- stage/*
- uat/*

- name: changed-all
wait_timer: 10
reviewers:
- id: 2
type: User
deployment_branch_policy:
custom_branches:
- dev/*

- name: new-environment
wait_timer: 1
reviewers:
- id: 1
type: Team
- id: 2
type: User
deployment_branch_policy:
protected_branches: true

- name: unchanged-default-wait-timer

- name: unchanged-wait-timer
wait_timer: 1

- name: unchanged-reviewers-unsorted
reviewers:
- id: 2
type: User
- id: 1
type: Team

- name: unchanged-reviewers-sorted
reviewers:
- id: 1
type: Team
- id: 2
type: User

- name: unchanged-deployment-branch-policy
deployment_branch_policy:
custom_branches:
- dev/*
- dev-*

1 comment on commit 0ca994f

@vercel
Copy link

@vercel vercel bot commented on 0ca994f Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.