diff --git a/branch-protections.md b/branch-protections.md index 4df9b5b..6c39efb 100644 --- a/branch-protections.md +++ b/branch-protections.md @@ -20,16 +20,19 @@ 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 -./hack/list-checks.sh +./hack/list-checks.sh GeoNet ``` List checks for a specific GeoNet repos ```sh -./hack/list-checks.sh Actions base-images +./hack/list-checks.sh GeoNet Actions base-images ``` Some example output may look like @@ -37,13 +40,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 @@ -68,4 +64,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 GeoNet Actions base-images | ./hack/set-checks.sh ``` diff --git a/hack/list-checks.sh b/hack/list-checks.sh index 3f2ad5f..39d729f 100755 --- a/hack/list-checks.sh +++ b/hack/list-checks.sh @@ -4,6 +4,8 @@ set -o errexit set -o nounset set -o pipefail +ORG="${1:-GeoNet}" +shift REPOS="${@}" DEBUG=false @@ -15,12 +17,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[@]}" } @@ -29,7 +31,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[@]}" } @@ -76,7 +78,6 @@ get_checks() { REPO="$1" echo "$REPO:" CHECKS=() - get_status_checks "$REPO" get_workflow_checks "$REPO" ( for CHECK in "${CHECKS[@]}"; do @@ -87,7 +88,7 @@ get_checks() { if [ -n "$REPOS" ]; then for REPO in $REPOS; do - get_checks "GeoNet/$REPO" + get_checks "$ORG/$REPO" done exit $? fi diff --git a/hack/set-checks.sh b/hack/set-checks.sh new file mode 100755 index 0000000..cc0ea03 --- /dev/null +++ b/hack/set-checks.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +GHA_APPID="15368" # github actions integrates with github through github app integrations +CHECKS_TO_REPO="$(< /dev/stdin yq e)" + +APPLY="${1:-do-not-apply}" + +for REPO in $(echo "$CHECKS_TO_REPO" | yq e '. | keys | .[]'); do + export REPO="$REPO" # for yq env + CHECKS="$(echo "$CHECKS_TO_REPO" | 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