Skip to content

Commit fa8d2d7

Browse files
committed
Updated documentation and added example scenarios.
1 parent 0c5b35c commit fa8d2d7

File tree

3 files changed

+192
-10
lines changed

3 files changed

+192
-10
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ rulesets:
153153

154154
Notes:
155155
- For the same branch that is covered by multi-level branch protection rules, contexts defined at the org level are merged into the sub-org and repo level contexts, while contexts defined at the sub-org level are merged into the repo level contexts.
156+
- Rules from the sub-org level are merged into the repo level when their ruleset share the same name. Becareful not to define the same rule type in both levels as it will be rejected by GitHub.
156157
- When `{{EXTERNALLY_DEFINED}}` is defined for a new branch protection rule or ruleset configuration, they will be deployed with no status checks.
157158
- When an existing branch protection rule or ruleset configuration is amended with `{{EXTERNALLY_DEFINED}}`, the status checks in the existing rules in GitHub will remain as is.
158159

docs/status-checks.md

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
## Status checks inheritance across scopes
2+
3+
### Rulesets
4+
5+
The status checks between organisation and repository rulesets are independent of each together.
6+
7+
In the following examples, a common ruleset name is used at all levels. Repo1 and Repo2 are managed at the Sub-org level.
8+
9+
#### No custom checks
10+
```
11+
Org checks:
12+
Org Check
13+
Sub-org checks:
14+
Sub-org Check
15+
Repo checks for Repo2:
16+
Repo Check
17+
```
18+
19+
Status checks:
20+
- Newly deployed rules:
21+
- Org: Org Check
22+
- Repo1: Sub-org Check
23+
- Repo2: _Failed to deploy as required_status_checks can't be defined twice in both sub-org and repo level_
24+
- Updating status checks via GitHub UI:
25+
- Org: Status checks reverted back to safe settings
26+
- Repo1: Status checks reverted back to safe settings
27+
- Repo2: NA
28+
29+
#### No custom checks 2
30+
```
31+
Org checks:
32+
Org Check
33+
Sub-org checks:
34+
Sub-org Check
35+
Repo checks for Repo2:
36+
_NONE_
37+
```
38+
39+
Status checks:
40+
- Newly deployed rules:
41+
- Org: Org Check
42+
- Repo1: Sub-org Check
43+
- Repo2: _NONE_
44+
- Updating status checks via GitHub UI:
45+
- Org: Status checks reverted back to safe settings
46+
- Repo1: Status checks reverted back to safe settings
47+
- Repo2: Custom status checks are retained
48+
49+
**The remaining tests will leave Repo2 out of the Sub-org.**
50+
51+
#### Custom checks enabled at the Org and Sub-org level
52+
```
53+
Org:
54+
Org Check
55+
{{EXTERNALLY_DEFINED}}
56+
Sub-org checks:
57+
Sub-org Check
58+
{{EXTERNALLY_DEFINED}}
59+
Repo checks for Repo2:
60+
Repo Check
61+
```
62+
63+
Status checks:
64+
- Newly deployed rules:
65+
- Org: []
66+
- Repo1: []
67+
- Repo2: Repo Check
68+
- Updating status checks via GitHub UI:
69+
- Org: Custom status checks are retained
70+
- Repo1: Custom status checks are retained
71+
- Repo2: Status checks reverted back to safe settings
72+
73+
#### Custom checks enabled at the Repo level
74+
```
75+
Org:
76+
Org Check
77+
Sub-org checks:
78+
Sub-org Check
79+
Repo checks for Repo2:
80+
Repo Check
81+
{{EXTERNALLY_DEFINED}}
82+
```
83+
84+
Status checks:
85+
- Newly deployed rules:
86+
- Org: Org Check
87+
- Repo1: Sub-org Check
88+
- Repo2: []
89+
- Updating status checks via GitHub UI:
90+
- Org: Status checks reverted back to safe settings
91+
- Repo1: Status checks reverted back to safe settings
92+
- Repo2: Custom status checks are retained
93+
94+
95+
### Branch protection rules
96+
97+
In the following examples the `main` branch is protected at all levels. Repo1 and Repo2 are managed at the Sub-org level.
98+
99+
#### No custom checks
100+
```
101+
Org checks:
102+
Org Check
103+
Sub-org checks:
104+
Sub-org Check
105+
Repo checks for Repo2:
106+
Repo Check
107+
```
108+
109+
Status checks:
110+
- Newly deployed rules:
111+
- Repo1: Org Check, Sub-org Check
112+
- Repo2: Org Check, Sub-org Check, Repo Check
113+
- Updating status checks via GitHub UI:
114+
- Repo1: Status checks reverted back to safe settings
115+
- Repo2: Status checks reverted back to safe settings
116+
117+
#### Custom checks enabled at the Org level
118+
```
119+
Org:
120+
Org Check
121+
{{EXTERNALLY_DEFINED}}
122+
Sub-org checks:
123+
Sub-org Check
124+
Repo checks for Repo2:
125+
Repo Check
126+
```
127+
128+
Status checks:
129+
- Newly deployed rules:
130+
- Repo1: []
131+
- Repo2: []
132+
- Updating status checks via GitHub UI:
133+
- Repo1: Custom status checks are retained
134+
- Repo2: Custom status checks are retained
135+
136+
#### Custom checks enabled at the Sub-org level
137+
```
138+
Org:
139+
Org Check
140+
Sub-org checks:
141+
Sub-org Check
142+
{{EXTERNALLY_DEFINED}}
143+
Repo checks for Repo2:
144+
Repo Check
145+
```
146+
147+
Status checks:
148+
- Newly deployed rules:
149+
- Repo1: []
150+
- Repo2: []
151+
- Updating status checks via GitHub UI:
152+
- Repo1: Custom status checks are retained
153+
- Repo2: Custom status checks are retained
154+
155+
#### Custom checks enabled at the Repo level
156+
```
157+
Org:
158+
Org Check
159+
Sub-org checks:
160+
Sub-org Check
161+
Repo checks for Repo2:
162+
Repo Check
163+
{{EXTERNALLY_DEFINED}}
164+
```
165+
166+
Status checks:
167+
- Newly deployed rules:
168+
- Repo1: Org Check, Sub-org Check
169+
- Repo2: []
170+
- Updating status checks via GitHub UI:
171+
- Repo1: Status checks reverted back to safe settings
172+
- Repo2: Custom status checks are retained

lib/plugins/overrides.js

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const ErrorStash = require('./errorStash')
22

33
module.exports = class Overrides extends ErrorStash {
4-
static getObjectRef (data, dataKey) {
4+
// Find all object references for a given key from the source.
5+
static getObjectRef (source, dataKey) {
56
const results = []
67
const traverse = (obj) => {
78
for (const key in obj) {
@@ -14,11 +15,13 @@ module.exports = class Overrides extends ErrorStash {
1415
}
1516
}
1617
}
17-
traverse(data)
18+
traverse(source)
1819
return results
1920
}
2021

21-
static findParentObj (source, child, remove = false) {
22+
// Find the parent object reference for a given child object and
23+
// allow the option to remove the parent object from the source.
24+
static getParentObjectRef (source, child, remove = false) {
2225
let parent = null
2326
const traverse = (obj, parentObj = null, parentKey = '') => {
2427
for (const key in obj) {
@@ -34,9 +37,9 @@ module.exports = class Overrides extends ErrorStash {
3437
if (remove) {
3538
obj[key].splice(index, 1)
3639
}
37-
} else {
38-
traverse(element)
40+
return
3941
}
42+
traverse(element)
4043
})
4144
} else if (typeof obj[key] === 'object' && obj[key]) {
4245
traverse(obj[key], obj, key)
@@ -47,19 +50,23 @@ module.exports = class Overrides extends ErrorStash {
4750
return parent
4851
}
4952

50-
static removeParentObj (source, child, levels) {
53+
// Traverse the source and remove the top level parent object
54+
static removeTopLevelParent (source, child, levels) {
5155
let parent = child
5256
for (let i = 0; i < levels; i++) {
5357
if (i + 1 === levels) {
54-
parent = Overrides.findParentObj(source, parent, true)
58+
parent = Overrides.getParentObjectRef(source, parent, true)
5559
} else {
56-
parent = Overrides.findParentObj(source, parent, false)
60+
parent = Overrides.getParentObjectRef(source, parent, false)
5761
}
5862
}
5963
}
6064

6165
// When {{EXTERNALLY_DEFINED}} is found in the override value, retain the
62-
// existing value from GitHub.
66+
// existing value from GitHub. If GitHub does not have a value, then
67+
// - A/ If the action is delete, then remove the top level parent object
68+
// and the override value from the source.
69+
// - B/ Otherwise, initialise the value to an appropriate value.
6370
// Note:
6471
// - The admin settings could define multiple status check rules for a
6572
// ruleset, but the GitHub API retains one only, i.e. the last one.
@@ -69,13 +76,15 @@ module.exports = class Overrides extends ErrorStash {
6976
Object.entries(overrides).forEach(([override, props]) => {
7077
let sourceRefs = Overrides.getObjectRef(source, override)
7178
let data = JSON.stringify(sourceRefs)
79+
7280
if (data.includes('{{EXTERNALLY_DEFINED}}')) {
7381
let existingRefs = Overrides.getObjectRef(existing, override)
7482
sourceRefs.forEach(sourceRef => {
7583
if (existingRefs[0]) {
7684
sourceRef[override] = existingRefs[0][override]
7785
} else if (props['action'] === 'delete') {
78-
Overrides.removeParentObj(source, sourceRef[override], props['parents'])
86+
Overrides.removeTopLevelParent(source, sourceRef[override], props['parents'])
87+
delete sourceRef[override]
7988
} else if (props['type'] === 'array') {
8089
sourceRef[override] = []
8190
} else if (props['type'] === 'dict') {

0 commit comments

Comments
 (0)