Skip to content

Commit e584dbc

Browse files
authored
Add support for GitHub Environments (#544)
* Add support for configuring environments * Add null handling in mergeDeep * Add custom_branch_policies handling * Add unit testing for Environments plugin
1 parent 64d21b6 commit e584dbc

File tree

4 files changed

+552
-2
lines changed

4 files changed

+552
-2
lines changed

lib/mergeDeep.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class MergeDeep {
8080
}
8181

8282
// If the target is empty, then all the source is added to additions
83-
if (t === undefined || (this.isEmpty(t) && !this.isEmpty(s))) {
83+
if (t === undefined || t === null || (this.isEmpty(t) && !this.isEmpty(s))) {
8484
additions = Object.assign(additions, s)
8585
return ({ additions, modifications, hasChanges: true })
8686
}

lib/plugins/environments.js

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
const Diffable = require('./diffable')
2+
3+
module.exports = class Environments extends Diffable {
4+
constructor(...args) {
5+
super(...args)
6+
7+
if (this.entries) {
8+
// Force all names to lowercase to avoid comparison issues.
9+
this.entries.forEach(environment => {
10+
environment.name = environment.name.toLowerCase();
11+
if(environment.variables) {
12+
environment.variables.forEach(variable => {
13+
variable.name = variable.name.toLowerCase();
14+
});
15+
}
16+
})
17+
}
18+
}
19+
20+
async find() {
21+
const { data: { environments } } = await this.github.request('GET /repos/:org/:repo/environments', {
22+
org: this.repo.owner,
23+
repo: this.repo.repo
24+
});
25+
26+
let environments_mapped = [];
27+
28+
for(let environment of environments) {
29+
const mapped = {
30+
name: environment.name.toLowerCase(),
31+
repo: this.repo.repo,
32+
wait_timer: (environment.protection_rules.find(rule => rule.type === 'wait_timer') || { wait_timer: 0 }).wait_timer,
33+
prevent_self_review: (environment.protection_rules.find(rule => rule.type === 'required_reviewers') || { prevent_self_review: false }).prevent_self_review,
34+
reviewers: (environment.protection_rules.find(rule => rule.type === 'required_reviewers') || { reviewers: [] }).reviewers.map(reviewer => ({id: reviewer.reviewer.id, type: reviewer.type})),
35+
deployment_branch_policy: environment.deployment_branch_policy === null ? null : {
36+
protected_branches: (environment.deployment_branch_policy || { protected_branches: false }).protected_branches,
37+
custom_branch_policies: (environment.deployment_branch_policy || { custom_branch_policies: false }).custom_branch_policies && (await this.github.request('GET /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
38+
org: this.repo.owner,
39+
repo: this.repo.repo,
40+
environment_name: environment.name
41+
})).data.branch_policies.map(policy => ({
42+
name: policy.name
43+
}))
44+
},
45+
variables: (await this.github.request('GET /repos/:org/:repo/environments/:environment_name/variables', {
46+
org: this.repo.owner,
47+
repo: this.repo.repo,
48+
environment_name: environment.name
49+
})).data.variables.map(variable => ({name: variable.name.toLowerCase(), value: variable.value})),
50+
deployment_protection_rules: (await this.github.request('GET /repos/:org/:repo/environments/:environment_name/deployment_protection_rules', {
51+
org: this.repo.owner,
52+
repo: this.repo.repo,
53+
environment_name: environment.name
54+
})).data.custom_deployment_protection_rules.map(rule => ({
55+
app_id: rule.app.id,
56+
id: rule.id
57+
}))
58+
}
59+
environments_mapped.push(mapped);
60+
//console.log(mapped);
61+
}
62+
63+
return environments_mapped;
64+
}
65+
66+
comparator(existing, attrs) {
67+
return existing.name === attrs.name
68+
}
69+
70+
getChanged(existing, attrs) {
71+
if (!attrs.wait_timer) attrs.wait_timer = 0;
72+
if (!attrs.prevent_self_review) attrs.prevent_self_review = false;
73+
if (!attrs.reviewers) attrs.reviewers = [];
74+
if (!attrs.deployment_branch_policy) attrs.deployment_branch_policy = null;
75+
if(!attrs.variables) attrs.variables = [];
76+
if(!attrs.deployment_protection_rules) attrs.deployment_protection_rules = [];
77+
78+
const wait_timer = existing.wait_timer !== attrs.wait_timer;
79+
const prevent_self_review = existing.prevent_self_review !== attrs.prevent_self_review;
80+
const reviewers = JSON.stringify(existing.reviewers.sort((x1, x2) => x1.id - x2.id)) !== JSON.stringify(attrs.reviewers.sort((x1, x2) => x1.id - x2.id));
81+
82+
let existing_custom_branch_policies = existing.deployment_branch_policy === null ? null : existing.deployment_branch_policy.custom_branch_policies;
83+
if(typeof(existing_custom_branch_policies) === 'object' && existing_custom_branch_policies !== null) {
84+
existing_custom_branch_policies = existing_custom_branch_policies.sort();
85+
}
86+
let attrs_custom_branch_policies = attrs.deployment_branch_policy === null ? null : attrs.deployment_branch_policy.custom_branch_policies;
87+
if(typeof(attrs_custom_branch_policies) === 'object' && attrs_custom_branch_policies !== null) {
88+
attrs_custom_branch_policies = attrs_custom_branch_policies.sort();
89+
}
90+
let deployment_branch_policy;
91+
if(existing.deployment_branch_policy === attrs.deployment_branch_policy) {
92+
deployment_branch_policy = false;
93+
}
94+
else {
95+
deployment_branch_policy = (
96+
(existing.deployment_branch_policy === null && attrs.deployment_branch_policy !== null) ||
97+
(existing.deployment_branch_policy !== null && attrs.deployment_branch_policy === null) ||
98+
(existing.deployment_branch_policy.protected_branches !== attrs.deployment_branch_policy.protected_branches) ||
99+
(JSON.stringify(existing_custom_branch_policies) !== JSON.stringify(attrs_custom_branch_policies))
100+
);
101+
}
102+
103+
const variables = JSON.stringify(existing.variables.sort((x1, x2) => x1.name - x2.name)) !== JSON.stringify(attrs.variables.sort((x1, x2) => x1.name - x2.name));
104+
const deployment_protection_rules = JSON.stringify(existing.deployment_protection_rules.map(x => ({app_id: x.app_id})).sort((x1, x2) => x1.app_id - x2.app_id)) !== JSON.stringify(attrs.deployment_protection_rules.map(x => ({app_id: x.app_id})).sort((x1, x2) => x1.app_id - x2.app_id));
105+
106+
return {wait_timer, prevent_self_review, reviewers, deployment_branch_policy, variables, deployment_protection_rules};
107+
}
108+
109+
changed(existing, attrs) {
110+
const {wait_timer, prevent_self_review, reviewers, deployment_branch_policy, variables, deployment_protection_rules} = this.getChanged(existing, attrs);
111+
112+
return wait_timer || prevent_self_review || reviewers || deployment_branch_policy || variables || deployment_protection_rules;
113+
}
114+
115+
async update(existing, attrs) {
116+
const {wait_timer, prevent_self_review, reviewers, deployment_branch_policy, variables, deployment_protection_rules} = this.getChanged(existing, attrs);
117+
118+
if(wait_timer || prevent_self_review || reviewers || deployment_branch_policy) {
119+
await this.github.request(`PUT /repos/:org/:repo/environments/:environment_name`, {
120+
org: this.repo.owner,
121+
repo: this.repo.repo,
122+
environment_name: attrs.name,
123+
wait_timer: attrs.wait_timer,
124+
prevent_self_review: attrs.prevent_self_review,
125+
reviewers: attrs.reviewers,
126+
deployment_branch_policy: attrs.deployment_branch_policy === null ? null : {
127+
protected_branches: attrs.deployment_branch_policy.protected_branches,
128+
custom_branch_policies: !!attrs.deployment_branch_policy.custom_branch_policies
129+
}
130+
})
131+
}
132+
133+
if(deployment_branch_policy && attrs.deployment_branch_policy && attrs.deployment_branch_policy.custom_branch_policies) {
134+
const existingPolicies = (await this.github.request('GET /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
135+
org: this.repo.owner,
136+
repo: this.repo.repo,
137+
environment_name: attrs.name
138+
})).data.branch_policies;
139+
140+
for(let policy of existingPolicies) {
141+
await this.github.request('DELETE /repos/:org/:repo/environments/:environment_name/deployment-branch-policies/:branch_policy_id', {
142+
org: this.repo.owner,
143+
repo: this.repo.repo,
144+
environment_name: attrs.name,
145+
branch_policy_id: policy.id
146+
});
147+
}
148+
149+
for(let policy of attrs.deployment_branch_policy.custom_branch_policies) {
150+
await this.github.request('POST /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
151+
org: this.repo.owner,
152+
repo: this.repo.repo,
153+
environment_name: attrs.name,
154+
name: policy
155+
});
156+
}
157+
}
158+
159+
if(variables) {
160+
let existingVariables = [...existing.variables];
161+
for(let variable of attrs.variables) {
162+
const existingVariable = existingVariables.find((_var) => _var.name === variable.name);
163+
if(existingVariable) {
164+
existingVariables = existingVariables.filter(_var => _var.name !== variable.name);
165+
if(existingVariable.value !== variable.value) {
166+
await this.github.request(`PATCH /repos/:org/:repo/environments/:environment_name/variables/:variable_name`, {
167+
org: this.repo.owner,
168+
repo: this.repo.repo,
169+
environment_name: attrs.name,
170+
variable_name: variable.name,
171+
value: variable.value
172+
});
173+
}
174+
}
175+
else {
176+
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/variables`, {
177+
org: this.repo.owner,
178+
repo: this.repo.repo,
179+
environment_name: attrs.name,
180+
name: variable.name,
181+
value: variable.value
182+
});
183+
}
184+
}
185+
186+
for(let variable of existingVariables) {
187+
await this.github.request('DELETE /repos/:org/:repo/environments/:environment_name/variables/:variable_name', {
188+
org: this.repo.owner,
189+
repo: this.repo.repo,
190+
environment_name: attrs.name,
191+
variable_name: variable.name
192+
});
193+
}
194+
}
195+
196+
if(deployment_protection_rules) {
197+
let existingRules = [...existing.deployment_protection_rules];
198+
for(let rule of attrs.deployment_protection_rules) {
199+
const existingRule = existingRules.find((_rule) => _rule.id === rule.id);
200+
201+
if(!existingRule) {
202+
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/deployment_protection_rules`, {
203+
org: this.repo.owner,
204+
repo: this.repo.repo,
205+
environment_name: attrs.name,
206+
integration_id: rule.app_id
207+
});
208+
}
209+
}
210+
211+
for(let rule of existingRules) {
212+
await this.github.request('DELETE /repos/:org/:repo/environments/:environment_name/deployment_protection_rules/:rule_id', {
213+
org: this.repo.owner,
214+
repo: this.repo.repo,
215+
environment_name: attrs.name,
216+
rule_id: rule.id
217+
});
218+
}
219+
}
220+
}
221+
222+
async add(attrs) {
223+
await this.github.request(`PUT /repos/:org/:repo/environments/:environment_name`, {
224+
org: this.repo.owner,
225+
repo: this.repo.repo,
226+
environment_name: attrs.name,
227+
wait_timer: attrs.wait_timer,
228+
prevent_self_review: attrs.prevent_self_review,
229+
reviewers: attrs.reviewers,
230+
deployment_branch_policy: attrs.deployment_branch_policy === null ? null : {
231+
protected_branches: attrs.deployment_branch_policy.protected_branches,
232+
custom_branch_policies: !!attrs.deployment_branch_policy.custom_branch_policies
233+
}
234+
});
235+
236+
if(attrs.deployment_branch_policy && attrs.deployment_branch_policy.custom_branch_policies) {
237+
for(let policy of attrs.deployment_branch_policy.custom_branch_policies) {
238+
await this.github.request('POST /repos/:org/:repo/environments/:environment_name/deployment-branch-policies', {
239+
org: this.repo.owner,
240+
repo: this.repo.repo,
241+
environment_name: attrs.name,
242+
name: policy.name
243+
});
244+
}
245+
}
246+
247+
248+
for(let variable of attrs.variables) {
249+
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/variables`, {
250+
org: this.repo.owner,
251+
repo: this.repo.repo,
252+
environment_name: attrs.name,
253+
name: variable.name,
254+
value: variable.value
255+
});
256+
}
257+
258+
for(let rule of attrs.deployment_protection_rules) {
259+
await this.github.request(`POST /repos/:org/:repo/environments/:environment_name/deployment_protection_rules`, {
260+
org: this.repo.owner,
261+
repo: this.repo.repo,
262+
environment_name: attrs.name,
263+
integration_id: rule.app_id
264+
});
265+
}
266+
}
267+
268+
async remove(existing) {
269+
await this.github.request(`DELETE /repos/:org/:repo/environments/:environment_name`, {
270+
org: this.repo.owner,
271+
repo: this.repo.repo,
272+
environment_name: existing.name
273+
});
274+
}
275+
}

lib/settings.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,8 @@ Settings.PLUGINS = {
786786
branches: require('./plugins/branches'),
787787
autolinks: require('./plugins/autolinks'),
788788
validator: require('./plugins/validator'),
789-
rulesets: require('./plugins/rulesets')
789+
rulesets: require('./plugins/rulesets'),
790+
environments: require('./plugins/environments')
790791
}
791792

792793
module.exports = Settings

0 commit comments

Comments
 (0)