Skip to content

Commit

Permalink
chore: add script to write branchprotector checks
Browse files Browse the repository at this point in the history
from the format of ./hack/list-checks.sh
  • Loading branch information
BobyMCbobs authored and quiffman committed Oct 17, 2023
1 parent b88a5b5 commit b0bcc56
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 80 deletions.
49 changes: 48 additions & 1 deletion branch-protections.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ required for protection until it's run.
The search is unhelpful due to it not populating the list of
checks when trying to search without typing.

## GitHub tokens

A GitHub token is needed for applying branch protection.
The permissions required are:

- admin:org
- repo

## Determine a list of checks

Use the helper script to get a mostly-concreate set of values
Expand Down Expand Up @@ -77,5 +85,44 @@ GeoNet/base-images:
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
./hack/list-checks.sh | ./hack/set-bp-yaml-checks.sh bp-config.yaml sow
```

every subsequent time, a command such as the following should be used to only update the check contexts

```sh
./hack/list-checks.sh | ./hack/set-bp-yaml-checks.sh bp-config.yaml update
```

## Applying the checks with branchprotector

the branch protection rules are able to be applied with

`go run`:

```sh
go run k8s.io/test-infra/prow/cmd/branchprotector@acf4a2e26b --github-token-path PATH_TO_TOKEN --config-path PATH_TO_CONFIG.yaml # --confirm
```

or with the container image

```sh
podman run -it --rm \
-v "PATH_TO_TOKEN:PATH_TO_TOKEN:row" \
-v "PATH_TO_CONFIG:PATH_TO_CONFIG:ro" \
gcr.io/k8s-prow/branchprotector:v20231011-acf4a2e26b \
--github-token-path PATH_TO_TOKEN \
--config-path PATH_TO_CONFIG.yaml # --confirm
```

ghproxy can also be used to cache the GitHub responses to save on tokens

```sh
podman run -it --rm -v ghproxy:/cache -p 8888:8888 gcr.io/k8s-prow/ghproxy:v20231011-33fbc60185 --cache-dir=/cache --cache-sizeGB=99
```

then add the following args to branchprotector

```sh
--github-endpoint=http://localhost:8888 --github-endpoint=https://github.com
```
31 changes: 20 additions & 11 deletions hack/list-checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ __debug_echo() {
}

# return a list under the ORG of repos with GitHub Actions workflows
get_repos_with_actions() {
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/$ORG/{}/contents/.github/workflows\" --jq \". | length | . > 0\" 2>&1>/dev/null && echo $ORG/{}" \
| grep -E "^$ORG/.*" | cat))
get_repos() {
repos=($(gh api "orgs/$ORG/repos" --jq '.[] | select(.fork==false) | select(.archived==false) | .owner.login + "/" + .name' --paginate | sort))
echo "${repos[@]}"
}

has_repo_github_actions() {
REPO="$1"
if gh api "repos/$REPO/contents/.github/workflows" --jq ". | length | . > 0" >/dev/null 2>&1; then
echo true
return
fi
echo false
}

# given a repo and offset, return the number of the latest merged PR made by a human
get_pull_request_numbers() {
REPO="$1"
Expand Down Expand Up @@ -58,7 +62,7 @@ get_status_checks() {
__debug_echo " - PR commit: $COMMIT"
while read CONTEXT; do
checks+=("$CONTEXT")
done < <(gh api "repos/$REPO/commits/$COMMIT/status" --jq '.statuses[].context' | grep -vi travis)
done < <(gh api "repos/$REPO/commits/$COMMIT/status" --jq '.statuses[].context' | grep -viE '^travis|conform/')
done
CHECKS+=("${checks[@]}")
}
Expand All @@ -85,7 +89,7 @@ get_workflow_checks() {
while read RUN; do
__debug_echo " - Check run: $RUN"
checks+=("$RUN")
done < <(gh api "repos/$REPO/check-suites/$SUITE/check-runs" --jq .check_runs[].name | grep -vi travis)
done < <(gh api "repos/$REPO/check-suites/$SUITE/check-runs" --jq '.check_runs[].name' | grep -viE 'travis|\$\{\{ matrix..* \}\}')
done < <(gh api "repos/$REPO/commits/$COMMIT/check-suites" --jq .check_suites[].id)
done
# if no checks are found, try one earlier than the latest PR
Expand All @@ -103,7 +107,12 @@ get_checks() {
REPO="$1"
printf "$REPO:"
CHECKS=()
get_workflow_checks "$REPO"
if [ ! "$(get_pull_request_numbers "$REPO")" = "0" ]; then
get_status_checks "$REPO"
if [ $(has_repo_github_actions "$REPO") = true ]; then
get_workflow_checks "$REPO"
fi
fi
if [[ -z ${CHECKS[*]} ]]; then
echo ' []'
else
Expand All @@ -123,6 +132,6 @@ if [ -n "$REPOS" ]; then
exit $?
fi

for REPO in $(get_repos_with_actions); do
for REPO in $(get_repos); do
get_checks "$REPO"
done
79 changes: 79 additions & 0 deletions hack/set-bp-yaml-checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/bin/bash

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

# NOTE must be in format from ./hack/list-checks.sh
INPUT="$(< /dev/stdin yq e)"
# NOTE only manages the fields
# .branch-protection.orgs.ORG.repos.REPO.branches.DEFAULT
# - .protect
# - .required_status_checks.contexts
OUTPUT="${1:-}"
ACTION="${2:-update}"

if [ -z "$OUTPUT" ]; then
cat <<EOF
usage: $0 OUTPUTFILE.yaml [update|sow]
EOF
exit 1
fi

if [ ! -f "$OUTPUT" ]; then
touch "$OUTPUT"
fi

if [ ! "$ACTION" = "sow" ]; then
cat <<EOF
NOTE: using update mode. To write the state-of-the-world use
$ $0 $1 sow
EOF
fi

for REPO in $(echo "$INPUT" | yq e '. | keys | .[]'); do
ORG="$(gh api repos/$REPO --jq '.owner.login')"
export ORG
REPO="$(gh api repos/$REPO --jq '.name')"
export REPO
CHECKS="$(echo "$INPUT" | yq e '.[env(ORG) + "/" + env(REPO)]' -o json | jq -rcM)"
export CHECKS
echo "$REPO : $CHECKS"
PROTECTED_BRANCHES=($(gh api -X GET repos/$ORG/$REPO/branches --jq '.[] | select(.protected==true) | .name'))

for BRANCH in "${PROTECTED_BRANCHES[@]}"; do
if [ "$ACTION" = "sow" ]; then
EBP="$(gh api -X GET repos/$ORG/$REPO/branches/$BRANCH/protection | jq | yq e -P)"
export EBP BRANCH
yq e -P -i 'with(.branch-protection.orgs[strenv(ORG)].repos[strenv(REPO)].branches[strenv(BRANCH)];
.enforce_admins = (env(EBP) | .enforce_admins.enabled)
| .required_linear_history = (env(EBP) | .required_linear_history.enabled)
| .allow_force_pushes = (env(EBP) | .allow_force_pushes.enabled)
| .allow_deletions = (env(EBP) | .allow_deletions.enabled)
| .required_linear_history = (env(EBP) | .required_linear_history.enabled)
| .required_pull_request_reviews = (env(EBP) | .required_pull_request_reviews)
| .required_pull_request_reviews.dismissal_restrictions.users = ([.required_pull_request_reviews.dismissal_restrictions.users[] | .login])
| .required_pull_request_reviews.dismissal_restrictions.teams = ([.required_pull_request_reviews.dismissal_restrictions.teams[] | .slug])
| . |= (with(select(.required_pull_request_reviews.dismissal_restrictions.users | length | . == 0) | select(.required_pull_request_reviews.dismissal_restrictions.teams | length | . == 0); del .required_pull_request_reviews.dismissal_restrictions))
| del .required_pull_request_reviews.url
| del .required_pull_request_reviews.dismissal_restrictions.url
| del .required_pull_request_reviews.dismissal_restrictions.users_url
| del .required_pull_request_reviews.dismissal_restrictions.teams_url
| .required_pull_request_reviews.bypass_pull_request_allowances.users = ([.required_pull_request_reviews.bypass_pull_request_allowances.users[] | .login])
| .required_pull_request_reviews.bypass_pull_request_allowances.teams = ([.required_pull_request_reviews.bypass_pull_request_allowances.teams[] | .slug])
| . |= (with(select(.required_pull_request_reviews.bypass_pull_request_allowances.users | length | . == 0) | select(.required_pull_request_reviews.bypass_pull_request_allowances.teams | length | . == 0); del .required_pull_request_reviews.bypass_pull_request_allowances))
| .required_status_checks.strict = (env(EBP) | .required_status_checks.strict)
| .restrictions.users = ([env(EBP) | .restrictions.users[] | .login])
| .restrictions.teams = ([env(EBP) | .restrictions.teams[] | .slug])
| . |= (with(select(.restrictions.users | length | . == 0) | select(.restrictions.teams | length | . == 0); del .restrictions))
| .protect = true | .required_status_checks.contexts = env(CHECKS))' "$OUTPUT"
else
export BRANCH
echo "$ORG $REPO $BRANCH $CHECKS"
yq e -P -i '.branch-protection.orgs[strenv(ORG)].repos[strenv(REPO)].branches[strenv(BRANCH)].protect = true
| .branch-protection.orgs[strenv(ORG)].repos[strenv(REPO)].branches[strenv(BRANCH)].required_status_checks.contexts = env(CHECKS)' "$OUTPUT"
fi
done
done
68 changes: 0 additions & 68 deletions hack/set-checks.sh

This file was deleted.

0 comments on commit b0bcc56

Please sign in to comment.