Skip to content

Commit

Permalink
Updated ruleset overrides based on the latest api change.
Browse files Browse the repository at this point in the history
  • Loading branch information
jitran committed Jan 25, 2025
1 parent c8926b8 commit 0c5b35c
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 17 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,15 @@ rulesets:
```

Notes:
- For branch protection rules, contexts defined at the org level are merged together with the sub-org and repo level contexts.
- 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.
- 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.

⚠️ **Warning:**
When `{{EXTERNALLY_DEFINED}}` is removed from an existing branch protection rule or ruleset configuration, the status checks in the existing rules in GitHub will revert to the checks that are defined in safe-settings.
> ⚠️ **Warning:**
When `{{EXTERNALLY_DEFINED}}` is removed from an existing branch protection rule or ruleset configuration, the status checks in the existing rules in GitHub will revert to the checks that are defined in safe-settings. From this point onwards, all status checks configured through the GitHub UI will be reverted back to the safe-settings configuration.

#### Status checks inheritance across scopes
Refer to [Status checks](docs/status-checks.md).

### Performance
When there are 1000s of repos to be managed -- and there is a global settings change -- safe-settings will have to work efficiently and only make the necessary API calls.
Expand Down
9 changes: 6 additions & 3 deletions lib/plugins/branches.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const MergeDeep = require('../mergeDeep')
const Overrides = require('./overrides')
const ignorableFields = []
const previewHeaders = { accept: 'application/vnd.github.hellcat-preview+json,application/vnd.github.luke-cage-preview+json,application/vnd.github.zzzax-preview+json' }
const overrides = [
'contexts',
]
const overrides = {
'contexts': {
'action': 'reset',
'type': 'array'
},
}

module.exports = class Branches extends ErrorStash {
constructor (nop, github, repo, settings, log, errors) {
Expand Down
50 changes: 46 additions & 4 deletions lib/plugins/overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,46 @@ module.exports = class Overrides extends ErrorStash {
return results
}

static findParentObj (source, child, remove = false) {
let parent = null
const traverse = (obj, parentObj = null, parentKey = '') => {
for (const key in obj) {
if (obj[key] === child) {
parent = obj
if (remove && parentObj && parentKey) {
delete parentObj[parentKey]
}
} else if (Array.isArray(obj[key])) {
obj[key].forEach((element, index) => {
if (element === child) {
parent = obj[key]
if (remove) {
obj[key].splice(index, 1)
}
} else {
traverse(element)
}
})
} else if (typeof obj[key] === 'object' && obj[key]) {
traverse(obj[key], obj, key)
}
}
}
traverse(source)
return parent
}

static removeParentObj (source, child, levels) {
let parent = child
for (let i = 0; i < levels; i++) {
if (i + 1 === levels) {
parent = Overrides.findParentObj(source, parent, true)
} else {
parent = Overrides.findParentObj(source, parent, false)
}
}
}

// When {{EXTERNALLY_DEFINED}} is found in the override value, retain the
// existing value from GitHub.
// Note:
Expand All @@ -26,20 +66,22 @@ module.exports = class Overrides extends ErrorStash {
// - The PUT method for rulesets (update) allows for multiple overrides.
// - The POST method for rulesets (create) allows for one override only.
static removeOverrides (overrides, source, existing) {
overrides.forEach(override => {
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 (Array.isArray(sourceRef[override])) {
} else if (props['action'] === 'delete') {
Overrides.removeParentObj(source, sourceRef[override], props['parents'])
} else if (props['type'] === 'array') {
sourceRef[override] = []
} else if (typeof sourceRef[override] === 'object' && sourceRef[override]) {
} else if (props['type'] === 'dict') {
sourceRef[override] = {}
} else {
sourceRef[override] = ''
throw new Error(`Unknown type ${props['type']} for ${override}`)
}
})
}
Expand Down
10 changes: 7 additions & 3 deletions lib/plugins/rulesets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ const NopCommand = require('../nopcommand')
const MergeDeep = require('../mergeDeep')
const Overrides = require('./overrides')
const ignorableFields = []
const overrides = [
'required_status_checks',
]
const overrides = {
'required_status_checks': {
'action': 'delete',
'parents': 3,
'type': 'dict'
},
}

const version = {
'X-GitHub-Api-Version': '2022-11-28'
Expand Down
14 changes: 10 additions & 4 deletions test/unit/lib/plugins/rulesets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const org_conditions = {
}

function generateRequestRuleset(id, name, conditions, checks, org=false) {
response = {
request = {
id: id,
name: name,
target: 'branch',
Expand All @@ -40,11 +40,14 @@ function generateRequestRuleset(id, name, conditions, checks, org=false) {
]
}
if (org) {
response.source_type = 'Organization'
request.source_type = 'Organization'
} else {
response.source_type = 'Repository'
request.source_type = 'Repository'
}
return response
if (checks.length === 0) {
request.rules = []
}
return request
}

function generateResponseRuleset(id, name, conditions, checks, org=false) {
Expand Down Expand Up @@ -73,6 +76,9 @@ function generateResponseRuleset(id, name, conditions, checks, org=false) {
response.owner = 'jitran'
response.repo = 'test'
}
if (checks.length === 0) {
response.rules = []
}
return response
}

Expand Down

0 comments on commit 0c5b35c

Please sign in to comment.