Skip to content

Commit

Permalink
Updated documentation and added example scenarios.
Browse files Browse the repository at this point in the history
  • Loading branch information
jitran committed Jan 25, 2025
1 parent 0c5b35c commit fa8d2d7
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ rulesets:

Notes:
- 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.
- 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.
- When `{{EXTERNALLY_DEFINED}}` is defined for a new branch protection rule or ruleset configuration, they will be deployed with no status checks.
- 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.

Expand Down
172 changes: 172 additions & 0 deletions docs/status-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
## Status checks inheritance across scopes

### Rulesets

The status checks between organisation and repository rulesets are independent of each together.

In the following examples, a common ruleset name is used at all levels. Repo1 and Repo2 are managed at the Sub-org level.

#### No custom checks
```
Org checks:
Org Check
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
Repo Check
```

Status checks:
- Newly deployed rules:
- Org: Org Check
- Repo1: Sub-org Check
- Repo2: _Failed to deploy as required_status_checks can't be defined twice in both sub-org and repo level_
- Updating status checks via GitHub UI:
- Org: Status checks reverted back to safe settings
- Repo1: Status checks reverted back to safe settings
- Repo2: NA

#### No custom checks 2
```
Org checks:
Org Check
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
_NONE_
```

Status checks:
- Newly deployed rules:
- Org: Org Check
- Repo1: Sub-org Check
- Repo2: _NONE_
- Updating status checks via GitHub UI:
- Org: Status checks reverted back to safe settings
- Repo1: Status checks reverted back to safe settings
- Repo2: Custom status checks are retained

**The remaining tests will leave Repo2 out of the Sub-org.**

#### Custom checks enabled at the Org and Sub-org level
```
Org:
Org Check
{{EXTERNALLY_DEFINED}}
Sub-org checks:
Sub-org Check
{{EXTERNALLY_DEFINED}}
Repo checks for Repo2:
Repo Check
```

Status checks:
- Newly deployed rules:
- Org: []
- Repo1: []
- Repo2: Repo Check
- Updating status checks via GitHub UI:
- Org: Custom status checks are retained
- Repo1: Custom status checks are retained
- Repo2: Status checks reverted back to safe settings

#### Custom checks enabled at the Repo level
```
Org:
Org Check
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
Repo Check
{{EXTERNALLY_DEFINED}}
```

Status checks:
- Newly deployed rules:
- Org: Org Check
- Repo1: Sub-org Check
- Repo2: []
- Updating status checks via GitHub UI:
- Org: Status checks reverted back to safe settings
- Repo1: Status checks reverted back to safe settings
- Repo2: Custom status checks are retained


### Branch protection rules

In the following examples the `main` branch is protected at all levels. Repo1 and Repo2 are managed at the Sub-org level.

#### No custom checks
```
Org checks:
Org Check
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
Repo Check
```

Status checks:
- Newly deployed rules:
- Repo1: Org Check, Sub-org Check
- Repo2: Org Check, Sub-org Check, Repo Check
- Updating status checks via GitHub UI:
- Repo1: Status checks reverted back to safe settings
- Repo2: Status checks reverted back to safe settings

#### Custom checks enabled at the Org level
```
Org:
Org Check
{{EXTERNALLY_DEFINED}}
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
Repo Check
```

Status checks:
- Newly deployed rules:
- Repo1: []
- Repo2: []
- Updating status checks via GitHub UI:
- Repo1: Custom status checks are retained
- Repo2: Custom status checks are retained

#### Custom checks enabled at the Sub-org level
```
Org:
Org Check
Sub-org checks:
Sub-org Check
{{EXTERNALLY_DEFINED}}
Repo checks for Repo2:
Repo Check
```

Status checks:
- Newly deployed rules:
- Repo1: []
- Repo2: []
- Updating status checks via GitHub UI:
- Repo1: Custom status checks are retained
- Repo2: Custom status checks are retained

#### Custom checks enabled at the Repo level
```
Org:
Org Check
Sub-org checks:
Sub-org Check
Repo checks for Repo2:
Repo Check
{{EXTERNALLY_DEFINED}}
```

Status checks:
- Newly deployed rules:
- Repo1: Org Check, Sub-org Check
- Repo2: []
- Updating status checks via GitHub UI:
- Repo1: Status checks reverted back to safe settings
- Repo2: Custom status checks are retained
29 changes: 19 additions & 10 deletions lib/plugins/overrides.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const ErrorStash = require('./errorStash')

module.exports = class Overrides extends ErrorStash {
static getObjectRef (data, dataKey) {
// Find all object references for a given key from the source.
static getObjectRef (source, dataKey) {
const results = []
const traverse = (obj) => {
for (const key in obj) {
Expand All @@ -14,11 +15,13 @@ module.exports = class Overrides extends ErrorStash {
}
}
}
traverse(data)
traverse(source)
return results
}

static findParentObj (source, child, remove = false) {
// Find the parent object reference for a given child object and
// allow the option to remove the parent object from the source.
static getParentObjectRef (source, child, remove = false) {
let parent = null
const traverse = (obj, parentObj = null, parentKey = '') => {
for (const key in obj) {
Expand All @@ -34,9 +37,9 @@ module.exports = class Overrides extends ErrorStash {
if (remove) {
obj[key].splice(index, 1)
}
} else {
traverse(element)
return
}
traverse(element)
})
} else if (typeof obj[key] === 'object' && obj[key]) {
traverse(obj[key], obj, key)
Expand All @@ -47,19 +50,23 @@ module.exports = class Overrides extends ErrorStash {
return parent
}

static removeParentObj (source, child, levels) {
// Traverse the source and remove the top level parent object
static removeTopLevelParent (source, child, levels) {
let parent = child
for (let i = 0; i < levels; i++) {
if (i + 1 === levels) {
parent = Overrides.findParentObj(source, parent, true)
parent = Overrides.getParentObjectRef(source, parent, true)
} else {
parent = Overrides.findParentObj(source, parent, false)
parent = Overrides.getParentObjectRef(source, parent, false)
}
}
}

// When {{EXTERNALLY_DEFINED}} is found in the override value, retain the
// existing value from GitHub.
// existing value from GitHub. If GitHub does not have a value, then
// - A/ If the action is delete, then remove the top level parent object
// and the override value from the source.
// - B/ Otherwise, initialise the value to an appropriate value.
// Note:
// - The admin settings could define multiple status check rules for a
// ruleset, but the GitHub API retains one only, i.e. the last one.
Expand All @@ -69,13 +76,15 @@ module.exports = class Overrides extends ErrorStash {
Object.entries(overrides).forEach(([override, props]) => {
let sourceRefs = Overrides.getObjectRef(source, override)
let data = JSON.stringify(sourceRefs)

if (data.includes('{{EXTERNALLY_DEFINED}}')) {
let existingRefs = Overrides.getObjectRef(existing, override)
sourceRefs.forEach(sourceRef => {
if (existingRefs[0]) {
sourceRef[override] = existingRefs[0][override]
} else if (props['action'] === 'delete') {
Overrides.removeParentObj(source, sourceRef[override], props['parents'])
Overrides.removeTopLevelParent(source, sourceRef[override], props['parents'])
delete sourceRef[override]
} else if (props['type'] === 'array') {
sourceRef[override] = []
} else if (props['type'] === 'dict') {
Expand Down

0 comments on commit fa8d2d7

Please sign in to comment.