Skip to content

Commit

Permalink
feat: Add support for environments
Browse files Browse the repository at this point in the history
  • Loading branch information
rlmartin committed Jan 30, 2023
1 parent 97c12a3 commit c7a4d03
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ 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
environments:
- name: production
wait_timer: 5
reviewers:
- id: 1
type: 'Team'
- id: 2
type: 'User'
deployment_branch_policy:
protected_branches: true
custom_branch_policies: false
- name: development
deployment_branch_policy:
protected_branches: false
custom_branch_policies: true

# See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options
teams:
- name: core
Expand Down
106 changes: 106 additions & 0 deletions lib/plugins/environments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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 environments.map(environment => {
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)
)
}

update (existing, attrs) {
return this.add(attrs)
}

add (attrs) {
return this.github.request(`PUT ${environmentRepoEndpoint}`, this.toParams({ name: attrs.name }, attrs))
}

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({
custom_branch_policies: attrs.custom_branch_policies,
protected_branches: attrs.protected_branches
})
}
}

toParams (existing, attrs) {
return {
environment_name: existing.name,
repo: this.repo.repo,
org: this.repo.owner,
wait_timer: attrs.wait_timer,
reviewers: attrs.reviewers,
deployment_branch_policy: attrs.deployment_branch_policy
}
}
}
1 change: 1 addition & 0 deletions lib/settings.js
Original file line number Diff line number Diff line change
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
80 changes: 80 additions & 0 deletions test/fixtures/environments-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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:
protected_branches: false
custom_branch_policies: true

- name: changed-all
wait_timer: 10
reviewers:
- id: 2
type: User
deployment_branch_policy:
protected_branches: false
custom_branch_policies: true

- name: new-environment
wait_timer: 1
reviewers:
- id: 1
type: Team
- id: 2
type: User
deployment_branch_policy:
protected_branches: false
custom_branch_policies: 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:
protected_branches: false
custom_branch_policies: true
166 changes: 166 additions & 0 deletions test/integration/plugins/environments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
const path = require('path')
const fs = require('fs')
const { CREATED, NO_CONTENT, OK } = require('http-status-codes')
const settings = require('../../../lib/settings')
const { buildTriggerEvent, initializeNock, loadInstance, repository, teardownNock } = require('../common')

describe('environments plugin', function () {
let probot, githubScope

beforeEach(async () => {
githubScope = initializeNock()
probot = await loadInstance()
})

afterEach(() => {
teardownNock(githubScope)
})

it('syncs environments', async () => {
const pathToConfig = path.resolve(__dirname, '..', '..', 'fixtures', 'environments-config.yml')
const configFile = Buffer.from(fs.readFileSync(pathToConfig, 'utf8'))
const config = configFile.toString()
githubScope
.get(`/repos/${repository.owner.name}/${repository.name}/contents/${encodeURIComponent(settings.FILE_NAME)}`)
.reply(OK, config)
githubScope.get(`/repos/${repository.owner.name}/${repository.name}/environments`).reply(OK, {
environments: [
{ name: 'changed-wait-timer', wait_timer: 1 },
{
name: 'changed-reviewers-type',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'changed-reviewers-id',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'changed-reviewers-remove',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'changed-reviewers-add',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'changed-deployment-branch-policy',
deployment_branch_policy: { protected_branches: true, custom_branch_policies: false }
},
{ name: 'changed-all', wait_timer: 0 },
{ name: 'unchanged-default-wait-timer', wait_timer: 0 },
{ name: 'unchanged-wait-timer', wait_timer: 1 },
{
name: 'unchanged-reviewers-unsorted',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'unchanged-reviewers-sorted',
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
]
},
{
name: 'unchanged-deployment-branch-policy',
deployment_branch_policy: { protected_branches: false, custom_branch_policies: true }
},
{ name: 'deleted', wait_timer: 0 }
]
})
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-wait-timer`, body => {
expect(body).toMatchObject({ wait_timer: 10 })
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-reviewers-type`, body => {
expect(body).toMatchObject({
reviewers: [
{ id: 1, type: 'User' },
{ id: 2, type: 'User' }
]
})
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-reviewers-id`, body => {
expect(body).toMatchObject({
reviewers: [
{ id: 1, type: 'Team' },
{ id: 3, type: 'User' }
]
})
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-reviewers-remove`, body => {
expect(body).toMatchObject({ reviewers: [{ id: 1, type: 'Team' }] })
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-reviewers-add`, body => {
expect(body).toMatchObject({
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' },
{ id: 3, type: 'User' }
]
})
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-deployment-branch-policy`, body => {
expect(body).toMatchObject({
deployment_branch_policy: { protected_branches: false, custom_branch_policies: true }
})
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/changed-all`, body => {
expect(body).toMatchObject({
wait_timer: 10,
reviewers: [{ id: 2, type: 'User' }],
deployment_branch_policy: { protected_branches: false, custom_branch_policies: true }
})
return true
})
.reply(CREATED)
githubScope
.put(`/repos/${repository.owner.name}/${repository.name}/environments/new-environment`, body => {
expect(body).toMatchObject({
wait_timer: 1,
reviewers: [
{ id: 1, type: 'Team' },
{ id: 2, type: 'User' }
],
deployment_branch_policy: { protected_branches: false, custom_branch_policies: true }
})
return true
})
.reply(CREATED)
githubScope.delete(`/repos/${repository.owner.name}/${repository.name}/environments/deleted`).reply(NO_CONTENT)

await probot.receive(buildTriggerEvent())
})
})

0 comments on commit c7a4d03

Please sign in to comment.