Skip to content

Commit

Permalink
chore: add script to reconcile branch protection
Browse files Browse the repository at this point in the history
pass through the output of ./hack/list-checks.sh to ./hack/set-checks.sh
and create or update the branch protection rule for the main branch
  • Loading branch information
BobyMCbobs committed Oct 8, 2023
1 parent c32964c commit ea7eddf
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 13 deletions.
22 changes: 15 additions & 7 deletions branch-protections.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ checks when trying to search without typing.
Use the helper script to get a mostly-concreate set of values
which may be set to provide check-based branch merge protection.

The list of checks is discovered through the check suites published
by GitHub Actions against the latest pull request made by a human.

List checks for all GeoNet repos

```sh
Expand All @@ -39,13 +42,6 @@ Some example output may look like
```yaml
GeoNet/Actions:
- commit-digest-vet / presubmit-workflow
- conform/commit/commit-body
- conform/commit/conventional-commit
- conform/commit/header-case
- conform/commit/header-last-character
- conform/commit/header-length
- conform/commit/imperative-mood
- conform/commit/spellcheck
- conform / conform
- lint-markdown / markdown-lint
- presubmit-readme-toc / presubmit-readme-toc
Expand All @@ -70,4 +66,16 @@ GeoNet/Actions:
- t9-no-push-check
- validate-schema / validate-github-actions
GeoNet/base-images:
- conform / conform
- presubmit-github-actions-workflow-validator / validate-github-actions
- presubmit-image-documented
- presubmit-image-exists
- presubmit-image-format
- presubmit-readme-toc / presubmit-readme-toc
```
Protection rules can be applied directly from what checks are present in the latest PR with
```sh
./hack/list-checks.sh Actions base-images | ./hack/set-checks.sh
```
12 changes: 6 additions & 6 deletions hack/list-checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -o errexit
set -o nounset
set -o pipefail

ORG="${GH_ORG:-GeoNet}"
REPOS="${@}"

DEBUG=false
Expand All @@ -15,12 +16,12 @@ __debug_echo() {
}

get_repos_with_actions() {
repos=($(gh api orgs/GeoNet/repos --jq '.[] | select(.fork==false) | select(.archived==false) | .name' --paginate \
repos=($(gh api "orgs/$ORG/repos" --jq '.[] | select(.fork==false) | select(.archived==false) | .name' --paginate \
| sort \
| tr ' ' '\n' \
| xargs -I{} \
sh -c 'gh api "repos/GeoNet/{}/contents/.github/workflows" --jq ". | length | . > 0" 2>&1>/dev/null && echo GeoNet/{}' \
| grep -E '^GeoNet/.*' | cat))
sh -c "gh api \"repos/$ORG/{}/contents/.github/workflows\" --jq \". | length | . > 0\" 2>&1>/dev/null && echo $ORG/{}" \
| grep -E "^$ORG/.*" | cat))
echo "${repos[@]}"
}

Expand All @@ -29,7 +30,7 @@ get_pull_request_numbers() {
PULL_REQUEST_NUMBERS=()
while read NUMBER; do
PULL_REQUEST_NUMBERS+=("$NUMBER")
done < <(gh api -X GET "repos/$REPO/pulls" -f state=all --jq .[0].number)
done < <(gh api -X GET "repos/$REPO/pulls" -f state=all --jq '.[] | select(.user.Bot!="type") | select(.user.login!="github-actions[bot]") | .number' | sort -r | uniq | head -n1)
echo "${PULL_REQUEST_NUMBERS[@]}"
}

Expand Down Expand Up @@ -76,7 +77,6 @@ get_checks() {
REPO="$1"
printf "$REPO:"
CHECKS=()
get_status_checks "$REPO"
get_workflow_checks "$REPO"
if [[ -z ${CHECKS[*]} ]]; then
echo ' []'
Expand All @@ -92,7 +92,7 @@ get_checks() {

if [ -n "$REPOS" ]; then
for REPO in $REPOS; do
get_checks "GeoNet/$REPO"
get_checks "$ORG/$REPO"
done
exit $?
fi
Expand Down
68 changes: 68 additions & 0 deletions hack/set-checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail

# NOTE Input must be from stdin and formatted like
# ORG/REPO:
# - check1
# - check2
INPUT="$(< /dev/stdin yq e)"
GHA_APPID="15368" # github actions integrates with github through github app integrations

APPLY="${1:-do-not-apply}"

for REPO in $(echo "$INPUT" | yq e '. | keys | .[]'); do
export REPO="$REPO" # for yq env
CHECKS="$(echo "$INPUT" | yq e '.[env(REPO)]' -o json | jq -rcM)"
echo "$REPO : $CHECKS"

ORG="$(gh api repos/$REPO --jq '.owner.login')"
DEFAULT_BRANCH="$(gh api "repos/$REPO" --jq .default_branch)"
if ! gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" ; then
UPDATED_CONFIG="$(jq -rcnM --arg CHECKS "$CHECKS" --arg ORG "$ORG" --arg GHA_APPID "$GHA_APPID" \
'{
"required_status_checks": {
"strict": true,
"checks": [($CHECKS | fromjson | .[] | {"context":.,"app_id":($GHA_APPID | tonumber)})]
},
"restrictions": {"users":[], "teams":[], "apps":[]},
"enforce_admins": null,
"required_pull_request_reviews": null
}')"
else
EXISTING_CONFIG="$(gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" | jq -rcM)"
# NOTE removes lots of fields from the original, since the api rejects them as non-null values.
# instead of constructing a new json object, it's based off of the current SOW
# to retain other fields that we don't care about in this update that might
# be in the original values.
UPDATED_CONFIG="$(echo "$EXISTING_CONFIG" \
| jq -rcM --arg CHECKS "$CHECKS" --arg ORG "$ORG" --arg GHA_APPID "$GHA_APPID" \
'.required_status_checks = {"strict":true, "checks": [($CHECKS | fromjson | .[] | {"context":.,"app_id":($GHA_APPID | tonumber)})]} |
.restrictions = {"users":[], "teams":[], "apps":[]} |
.enforce_admins=null | .required_signatures=null | .required_linear_history=null | .allow_deletions=null | .block_creations=null |
.required_conversation_resolution=null | .lock_branch=null | .allow_fork_syncing=null | .url=null | .required_pull_request_reviews=null | .allow_force_pushes=null
')"
fi

echo "Config difference:"
sdiff <(echo "$EXISTING_CONFIG" | jq) <(echo "$UPDATED_CONFIG" | jq) || true

if [ ! "$APPLY" = "apply-and-agree-to-risk" ]; then
echo "NOTE: dry run enabled"
echo "WARNING: applying may change unintended settings regarding branch protection for the target branch"
echo "to apply, use: $0 apply-and-agree-to-risk"
continue
fi

# NOTE gh api doesn't support this functionality
echo "Updating branch protection for $REPO on branch $DEFAULT_BRANCH"
curl -L \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $(gh auth token)" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
-d "$UPDATED_CONFIG"
done

0 comments on commit ea7eddf

Please sign in to comment.