From 79e847f887989b93046927438a018272cc8516c2 Mon Sep 17 00:00:00 2001 From: Alexander Olzem Date: Wed, 5 Aug 2026 16:12:11 +0200 Subject: [PATCH 1/2] feat: add parameters for branch protection rule customization --- README.md | 13 +++++++- common.mk | 82 ++++++++++++++++++++++++++++++++++-------------- docs/NEW_REPO.md | 8 +++++ 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b6b865b..0a167b3 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,18 @@ It configures: - **Labels** — creates/updates the standard set of issue and PR labels - **Merge strategy** — merge commits only (no squash or rebase), auto-merge enabled, delete branch on merge - **Secret scanning** — enabled -- **Branch protection** — a "protect-main" ruleset on the default branch: requires PRs with 1 approval, dismisses stale reviews, requires review thread resolution, enforces commit signatures, prevents direct pushes/deletions/non-fast-forwards (org admins can bypass) +- **Branch protection** — a "protect-main" ruleset on the default branch (and any `REPO_RULESET_BRANCHES` patterns): requires PRs with 1 approval, dismisses stale reviews, requires review thread resolution, enforces commit signatures, prevents direct pushes/deletions/non-fast-forwards + +The branch protection ruleset is configurable via make variables (set them in your `Makefile` or pass them on the command line, e.g. `make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_RULESET_BRANCHES='["release/*"]'`): + +| Variable | Default | Description | +| --- | --- | --- | +| `REPO_ADMIN_BYPASS` | `true` | When `false`, org admins cannot bypass the ruleset | +| `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | +| `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | +| `REPO_REQUIRE_BRANCH_UP_TO_DATE` | `false` | Require branches to be up to date before merging (needs at least one `REPO_STATUS_CHECKS` value; `repo-settings` fails otherwise) | +| `REPO_STATUS_CHECKS` | `[]` | JSON array of status-check contexts that must pass (e.g. `["CI","Check action pins"]`); each job name is used as-is, so contexts with spaces work; the `required_status_checks` rule is only added when this is non-empty | +| `REPO_RULESET_BRANCHES` | `[]` | JSON array of additional branch patterns the ruleset applies to (e.g. `["release/*"]`); short names are normalized to `refs/heads/...`; the default branch is always protected | ### Default git hooks diff --git a/common.mk b/common.mk index 19ac135..08a3a22 100644 --- a/common.mk +++ b/common.mk @@ -74,28 +74,19 @@ spike;b23adb;A task to research a question and resolve problems endef export REPO_LABELS -REPO_RULESET := { \ - "name": "protect-main", \ - "target": "branch", \ - "enforcement": "active", \ - "conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } }, \ - "rules": [ \ - { "type": "deletion" }, \ - { "type": "non_fast_forward" }, \ - { "type": "creation" }, \ - { "type": "required_signatures" }, \ - { "type": "pull_request", "parameters": { \ - "required_approving_review_count": 1, \ - "dismiss_stale_reviews_on_push": true, \ - "required_reviewers": [], \ - "require_code_owner_review": false, \ - "require_last_push_approval": false, \ - "required_review_thread_resolution": true, \ - "allowed_merge_methods": ["squash", "rebase", "merge"] \ - }} \ - ], \ - "bypass_actors": [{ "actor_type": "OrganizationAdmin", "bypass_mode": "always" }] \ -} +# Branch protection ruleset — configurable per repository. The default branch +# is always protected; REPO_RULESET_BRANCHES adds further branch patterns +# (a JSON array, e.g. '["release/*"]'; short names are normalized to refs/heads/...). +# REPO_STATUS_CHECKS is a JSON array of status-check contexts that must pass +# (each job name is used as-is, so contexts with spaces work). +# Booleans must be `true` or `false`. Example: +# make repo-settings REPO_ADMIN_BYPASS=false REPO_STATUS_CHECKS='["CI","Check action pins"]' REPO_RULESET_BRANCHES='["release/*"]' +REPO_ADMIN_BYPASS ?= true +REPO_REQUIRED_APPROVING_REVIEW_COUNT ?= 1 +REPO_REQUIRE_CODE_OWNER_REVIEW ?= false +REPO_REQUIRE_BRANCH_UP_TO_DATE ?= false +REPO_STATUS_CHECKS ?= [] +REPO_RULESET_BRANCHES ?= [] .PHONY: repo-settings repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, branch protection, security) @@ -122,12 +113,55 @@ repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, --input <(echo '{"security_and_analysis":{"secret_scanning":{"status":"enabled"}}}') > /dev/null; \ \ echo " Configuring branch protection ruleset..."; \ + if [ "$(REPO_REQUIRE_BRANCH_UP_TO_DATE)" = "true" ] && ! echo '$(REPO_STATUS_CHECKS)' | $(JQ) -e 'length > 0' > /dev/null 2>&1; then \ + echo "error: REPO_REQUIRE_BRANCH_UP_TO_DATE=true requires at least one value in REPO_STATUS_CHECKS"; \ + exit 1; \ + fi; \ + RULESET_JSON=$$($(JQ) -cn \ + --argjson branches '$(REPO_RULESET_BRANCHES)' \ + --argjson checks '$(REPO_STATUS_CHECKS)' \ + --argjson approvals '$(REPO_REQUIRED_APPROVING_REVIEW_COUNT)' \ + --argjson codeOwner '$(REPO_REQUIRE_CODE_OWNER_REVIEW)' \ + --argjson adminBypass '$(REPO_ADMIN_BYPASS)' \ + --argjson upToDate '$(REPO_REQUIRE_BRANCH_UP_TO_DATE)' \ + '($$branches | map(select(length > 0)) | map(if startswith("~") or startswith("refs/") then . else "refs/heads/" + . end)) as $$bs | \ + ($$checks | map(select(length > 0))) as $$cs | \ + { name: "protect-main", target: "branch", enforcement: "active", \ + conditions: { ref_name: { include: (["~DEFAULT_BRANCH"] + $$bs | unique), exclude: [] } }, \ + rules: ([ \ + { type: "deletion" }, \ + { type: "non_fast_forward" }, \ + { type: "creation" }, \ + { type: "required_signatures" }, \ + { type: "pull_request", parameters: { \ + required_approving_review_count: $$approvals, \ + dismiss_stale_reviews_on_push: true, \ + required_reviewers: [], \ + require_code_owner_review: $$codeOwner, \ + require_last_push_approval: false, \ + required_review_thread_resolution: true, \ + allowed_merge_methods: ["squash", "rebase", "merge"] \ + } } \ + ] + (if ($$cs | length > 0) then \ + [{ type: "required_status_checks", parameters: { \ + strict_required_status_checks_policy: $$upToDate, \ + required_status_checks: [$$cs | .[] | { context: . }] \ + } }] else [] end)), \ + bypass_actors: (if $$adminBypass then [{ actor_type: "OrganizationAdmin", bypass_mode: "always" }] else [] end) \ + }'); \ + echo " branches: $$(echo "$$RULESET_JSON" | $(JQ) -r '.conditions.ref_name.include | join(", ")')"; \ + echo " approvals: $(REPO_REQUIRED_APPROVING_REVIEW_COUNT)"; \ + echo " code owner review: $(REPO_REQUIRE_CODE_OWNER_REVIEW)"; \ + echo " branch up-to-date: $(REPO_REQUIRE_BRANCH_UP_TO_DATE)"; \ + echo " required status checks: $(REPO_STATUS_CHECKS)"; \ + echo " admin bypass: $(REPO_ADMIN_BYPASS)"; \ + \ existing=$$($(GH) api "repos/$$REPO/rulesets" -q '.[] | select(.name=="protect-main") | .id' 2>/dev/null); \ if [ -n "$$existing" ]; then \ - $(GH) api "repos/$$REPO/rulesets/$$existing" -X PUT --input <(echo '$(REPO_RULESET)') > /dev/null; \ + $(GH) api "repos/$$REPO/rulesets/$$existing" -X PUT --input <(echo "$$RULESET_JSON") > /dev/null; \ echo " Updated existing ruleset (id: $$existing)"; \ else \ - $(GH) api "repos/$$REPO/rulesets" -X POST --input <(echo '$(REPO_RULESET)') > /dev/null; \ + $(GH) api "repos/$$REPO/rulesets" -X POST --input <(echo "$$RULESET_JSON") > /dev/null; \ echo " Created new ruleset"; \ fi; \ \ diff --git a/docs/NEW_REPO.md b/docs/NEW_REPO.md index 8e88ec8..8219176 100644 --- a/docs/NEW_REPO.md +++ b/docs/NEW_REPO.md @@ -35,6 +35,14 @@ make repo-settings This reconciles labels, merge strategy (merge commits only, auto-merge enabled, delete branch on merge), secret scanning, and the `protect-main` branch ruleset. See `make help` for details. +The branch protection ruleset is configurable via make variables, e.g.: + +```sh +make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_REQUIRE_BRANCH_UP_TO_DATE=true REPO_RULESET_BRANCHES='["release/*"]' +``` + +See the README for the full list of `REPO_*` variables. + ## 4. Set up GitHub organization secrets The following secrets must be whitelisted for your repository at the organization level From 3dbedb800dbcd5bc0c614b1db7fa6493d975fc0e Mon Sep 17 00:00:00 2001 From: Alexander Olzem Date: Thu, 6 Aug 2026 14:40:21 +0200 Subject: [PATCH 2/2] refactor: move repo-settings into a dedicated script --- README.md | 7 +- common.mk | 134 ++++++----------------------------- docs/NEW_REPO.md | 2 +- scripts/repo-settings.sh | 146 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 114 deletions(-) create mode 100755 scripts/repo-settings.sh diff --git a/README.md b/README.md index 0a167b3..5c42905 100644 --- a/README.md +++ b/README.md @@ -100,14 +100,17 @@ Run `make repo-settings` to reconcile your GitHub repository with the organizati It configures: - **Labels** — creates/updates the standard set of issue and PR labels -- **Merge strategy** — merge commits only (no squash or rebase), auto-merge enabled, delete branch on merge +- **Merge strategy** — merge commits only by default (configurable via `REPO_ALLOW_MERGE_COMMIT`, `REPO_ALLOW_SQUASH_MERGE`, `REPO_ALLOW_REBASE_MERGE`), auto-merge enabled, delete branch on merge - **Secret scanning** — enabled - **Branch protection** — a "protect-main" ruleset on the default branch (and any `REPO_RULESET_BRANCHES` patterns): requires PRs with 1 approval, dismisses stale reviews, requires review thread resolution, enforces commit signatures, prevents direct pushes/deletions/non-fast-forwards -The branch protection ruleset is configurable via make variables (set them in your `Makefile` or pass them on the command line, e.g. `make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_RULESET_BRANCHES='["release/*"]'`): +The repository settings are configurable via make variables (set them in your `Makefile` or pass them on the command line, e.g. `make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_RULESET_BRANCHES='["release/*"]'`): | Variable | Default | Description | | --- | --- | --- | +| `REPO_ALLOW_MERGE_COMMIT` | `true` | Allow merge commits in the merge strategy | +| `REPO_ALLOW_SQUASH_MERGE` | `false` | Allow squash merging | +| `REPO_ALLOW_REBASE_MERGE` | `false` | Allow rebase merging | | `REPO_ADMIN_BYPASS` | `true` | When `false`, org admins cannot bypass the ruleset | | `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | | `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | diff --git a/common.mk b/common.mk index 08a3a22..6c9d6f0 100644 --- a/common.mk +++ b/common.mk @@ -52,33 +52,12 @@ SETUP_ENVTEST ?= $(LOCALGOBIN)/setup-envtest ##@ Repository -define REPO_LABELS -bug;d73a4a;Something isn't working -documentation;0075ca;Improvements or additions to documentation -duplicate;cfd3d7;This issue or pull request already exists -enhancement;a2eeef;New feature or request -good first issue;7057ff;Good for newcomers -help wanted;008672;Extra attention is needed -invalid;e4e669;This doesn't seem right -question;37326e;Further information is requested -wontfix;ffffff;This will not be worked on -chore;ededed;A routine task or common potentially re-occurring task -go;16e2e2;Pull requests that update go code -ok-to-helm;0e8a16;PR is allowed to build an publish helm chart -dependencies;0366d6;Pull requests that update a dependency file -github-actions;80c4c6;PR created via GitHub action -needs-triage;eab668;Issue that has not been reviewed -ok-to-image;0e8a16;PR is allowed to run container build -ok-to-test;0e8a16;PR is allowed to be tested -spike;b23adb;A task to research a question and resolve problems -endef -export REPO_LABELS - -# Branch protection ruleset — configurable per repository. The default branch -# is always protected; REPO_RULESET_BRANCHES adds further branch patterns -# (a JSON array, e.g. '["release/*"]'; short names are normalized to refs/heads/...). -# REPO_STATUS_CHECKS is a JSON array of status-check contexts that must pass -# (each job name is used as-is, so contexts with spaces work). +# repo-settings — repository configuration, configurable per repository. +# Branch protection: the default branch is always protected; REPO_RULESET_BRANCHES +# adds further branch patterns (a JSON array, e.g. '["release/*"]'; short names are +# normalized to refs/heads/...). REPO_STATUS_CHECKS is a JSON array of status-check +# contexts that must pass (each job name is used as-is, so contexts with spaces work). +# REPO_ALLOW_MERGE_COMMIT/SQUASH_MERGE/REBASE_MERGE configure the merge strategy. # Booleans must be `true` or `false`. Example: # make repo-settings REPO_ADMIN_BYPASS=false REPO_STATUS_CHECKS='["CI","Check action pins"]' REPO_RULESET_BRANCHES='["release/*"]' REPO_ADMIN_BYPASS ?= true @@ -87,93 +66,26 @@ REPO_REQUIRE_CODE_OWNER_REVIEW ?= false REPO_REQUIRE_BRANCH_UP_TO_DATE ?= false REPO_STATUS_CHECKS ?= [] REPO_RULESET_BRANCHES ?= [] +REPO_ALLOW_MERGE_COMMIT ?= true +REPO_ALLOW_SQUASH_MERGE ?= false +REPO_ALLOW_REBASE_MERGE ?= false .PHONY: repo-settings repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, branch protection, security) - @$(GH) auth status >/dev/null 2>&1 || { echo "error: gh is not authenticated; run 'gh auth login'"; exit 1; }; \ - REPO=$$($(GH) repo view --json nameWithOwner -q .nameWithOwner) || { echo "error: not a GitHub repository"; exit 1; }; \ - echo "Reconciling settings for $$REPO..."; \ - \ - echo " Syncing labels..."; \ - echo "$$REPO_LABELS" | while IFS=';' read -r name color desc; do \ - [ -z "$$name" ] && continue; \ - $(GH) label create "$$name" --repo "$$REPO" --color "$$color" --description "$$desc" --force 2>/dev/null; \ - done; \ - \ - echo " Configuring merge strategy..."; \ - $(GH) api "repos/$$REPO" -X PATCH \ - -f allow_merge_commit=true \ - -f allow_squash_merge=false \ - -f allow_rebase_merge=false \ - -f delete_branch_on_merge=true \ - -f allow_auto_merge=true > /dev/null; \ - \ - echo " Enabling secret scanning..."; \ - $(GH) api "repos/$$REPO" -X PATCH \ - --input <(echo '{"security_and_analysis":{"secret_scanning":{"status":"enabled"}}}') > /dev/null; \ - \ - echo " Configuring branch protection ruleset..."; \ - if [ "$(REPO_REQUIRE_BRANCH_UP_TO_DATE)" = "true" ] && ! echo '$(REPO_STATUS_CHECKS)' | $(JQ) -e 'length > 0' > /dev/null 2>&1; then \ - echo "error: REPO_REQUIRE_BRANCH_UP_TO_DATE=true requires at least one value in REPO_STATUS_CHECKS"; \ - exit 1; \ - fi; \ - RULESET_JSON=$$($(JQ) -cn \ - --argjson branches '$(REPO_RULESET_BRANCHES)' \ - --argjson checks '$(REPO_STATUS_CHECKS)' \ - --argjson approvals '$(REPO_REQUIRED_APPROVING_REVIEW_COUNT)' \ - --argjson codeOwner '$(REPO_REQUIRE_CODE_OWNER_REVIEW)' \ - --argjson adminBypass '$(REPO_ADMIN_BYPASS)' \ - --argjson upToDate '$(REPO_REQUIRE_BRANCH_UP_TO_DATE)' \ - '($$branches | map(select(length > 0)) | map(if startswith("~") or startswith("refs/") then . else "refs/heads/" + . end)) as $$bs | \ - ($$checks | map(select(length > 0))) as $$cs | \ - { name: "protect-main", target: "branch", enforcement: "active", \ - conditions: { ref_name: { include: (["~DEFAULT_BRANCH"] + $$bs | unique), exclude: [] } }, \ - rules: ([ \ - { type: "deletion" }, \ - { type: "non_fast_forward" }, \ - { type: "creation" }, \ - { type: "required_signatures" }, \ - { type: "pull_request", parameters: { \ - required_approving_review_count: $$approvals, \ - dismiss_stale_reviews_on_push: true, \ - required_reviewers: [], \ - require_code_owner_review: $$codeOwner, \ - require_last_push_approval: false, \ - required_review_thread_resolution: true, \ - allowed_merge_methods: ["squash", "rebase", "merge"] \ - } } \ - ] + (if ($$cs | length > 0) then \ - [{ type: "required_status_checks", parameters: { \ - strict_required_status_checks_policy: $$upToDate, \ - required_status_checks: [$$cs | .[] | { context: . }] \ - } }] else [] end)), \ - bypass_actors: (if $$adminBypass then [{ actor_type: "OrganizationAdmin", bypass_mode: "always" }] else [] end) \ - }'); \ - echo " branches: $$(echo "$$RULESET_JSON" | $(JQ) -r '.conditions.ref_name.include | join(", ")')"; \ - echo " approvals: $(REPO_REQUIRED_APPROVING_REVIEW_COUNT)"; \ - echo " code owner review: $(REPO_REQUIRE_CODE_OWNER_REVIEW)"; \ - echo " branch up-to-date: $(REPO_REQUIRE_BRANCH_UP_TO_DATE)"; \ - echo " required status checks: $(REPO_STATUS_CHECKS)"; \ - echo " admin bypass: $(REPO_ADMIN_BYPASS)"; \ - \ - existing=$$($(GH) api "repos/$$REPO/rulesets" -q '.[] | select(.name=="protect-main") | .id' 2>/dev/null); \ - if [ -n "$$existing" ]; then \ - $(GH) api "repos/$$REPO/rulesets/$$existing" -X PUT --input <(echo "$$RULESET_JSON") > /dev/null; \ - echo " Updated existing ruleset (id: $$existing)"; \ - else \ - $(GH) api "repos/$$REPO/rulesets" -X POST --input <(echo "$$RULESET_JSON") > /dev/null; \ - echo " Created new ruleset"; \ - fi; \ - \ - echo " Installing update-action-pins workflow..."; \ - mkdir -p .github/workflows; \ - _dev_kit_ver=$${DEV_KIT_VERSION:-main}; \ - curl --fail -sSL \ - "https://raw.githubusercontent.com/opendefensecloud/dev-kit/$$_dev_kit_ver/.github/workflows/update-action-pins.yml" \ - -o .github/workflows/update-action-pins.yml; \ - echo " Wrote .github/workflows/update-action-pins.yml"; \ - \ - echo "Done." + @curl --fail -sSL \ + "https://raw.githubusercontent.com/opendefensecloud/dev-kit/$(DEV_KIT_VERSION)/scripts/repo-settings.sh" | \ + REPO_ADMIN_BYPASS='$(REPO_ADMIN_BYPASS)' \ + REPO_ALLOW_MERGE_COMMIT='$(REPO_ALLOW_MERGE_COMMIT)' \ + REPO_ALLOW_REBASE_MERGE='$(REPO_ALLOW_REBASE_MERGE)' \ + REPO_ALLOW_SQUASH_MERGE='$(REPO_ALLOW_SQUASH_MERGE)' \ + REPO_REQUIRE_BRANCH_UP_TO_DATE='$(REPO_REQUIRE_BRANCH_UP_TO_DATE)' \ + REPO_REQUIRE_CODE_OWNER_REVIEW='$(REPO_REQUIRE_CODE_OWNER_REVIEW)' \ + REPO_REQUIRED_APPROVING_REVIEW_COUNT='$(REPO_REQUIRED_APPROVING_REVIEW_COUNT)' \ + REPO_RULESET_BRANCHES='$(REPO_RULESET_BRANCHES)' \ + REPO_STATUS_CHECKS='$(REPO_STATUS_CHECKS)' \ + DEV_KIT_VERSION='$(DEV_KIT_VERSION)' \ + GH='$(GH)' JQ='$(JQ)' \ + bash .PHONY: update-action-pins update-action-pins: ## Update GitHub Action pins to their latest commit SHA diff --git a/docs/NEW_REPO.md b/docs/NEW_REPO.md index 8219176..1796e52 100644 --- a/docs/NEW_REPO.md +++ b/docs/NEW_REPO.md @@ -33,7 +33,7 @@ Run: make repo-settings ``` -This reconciles labels, merge strategy (merge commits only, auto-merge enabled, delete branch on merge), secret scanning, and the `protect-main` branch ruleset. See `make help` for details. +This reconciles labels, merge strategy (merge commits only by default, configurable via the `REPO_ALLOW_*` variables; auto-merge enabled, delete branch on merge), secret scanning, and the `protect-main` branch ruleset. See `make help` for details. The branch protection ruleset is configurable via make variables, e.g.: diff --git a/scripts/repo-settings.sh b/scripts/repo-settings.sh new file mode 100755 index 0000000..4e06e55 --- /dev/null +++ b/scripts/repo-settings.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Reconcile a repository's GitHub settings: labels, merge strategy, secret +# scanning, branch protection ruleset, and the update-action-pins workflow. +# +# All configuration is passed through environment variables. Defaults are the +# single source of truth in common.mk and are always passed by the make target; +# an unset variable here is a configuration error (set -u). +# REPO_ADMIN_BYPASS when "false", org admins cannot bypass the ruleset +# REPO_REQUIRED_APPROVING_REVIEW_COUNT number of approving reviews required to merge +# REPO_REQUIRE_CODE_OWNER_REVIEW require an approving review from code owners +# REPO_REQUIRE_BRANCH_UP_TO_DATE require branches up to date (requires status checks) +# REPO_STATUS_CHECKS JSON array of required status-check contexts +# REPO_RULESET_BRANCHES JSON array of additional branch patterns +# REPO_ALLOW_MERGE_COMMIT allow merge commits in the merge strategy +# REPO_ALLOW_SQUASH_MERGE allow squash merging +# REPO_ALLOW_REBASE_MERGE allow rebase merging +# DEV_KIT_VERSION dev-kit version to fetch the workflow from +# GH, JQ commands used to talk to GitHub and build JSON + +"$GH" auth status >/dev/null 2>&1 || { + echo "error: gh is not authenticated; run 'gh auth login'" >&2 + exit 1 +} + +REPO=$("$GH" repo view --json nameWithOwner -q .nameWithOwner) || { + echo "error: not a GitHub repository" >&2 + exit 1 +} + +REPO_STATUS_CHECKS_EFFECTIVE=$(echo "$REPO_STATUS_CHECKS" | "$JQ" -c '[.[] | select((type == "string") and (length > 0))]') +REPO_RULESET_BRANCHES_EFFECTIVE=$(echo "$REPO_RULESET_BRANCHES" | "$JQ" -c '[.[] | + select((type == "string") and (length > 0)) | + if startswith("~") or startswith("refs/") then . + else "refs/heads/" + . end] | + ["~DEFAULT_BRANCH"] + . | unique') + +if [ "$REPO_REQUIRE_BRANCH_UP_TO_DATE" = "true" ] && + [ "$REPO_STATUS_CHECKS_EFFECTIVE" = "[]" ]; then + echo "error: REPO_REQUIRE_BRANCH_UP_TO_DATE=true requires at least one value in REPO_STATUS_CHECKS" >&2 + exit 1 +fi + +echo "Reconciling settings for $REPO..." + +echo " Syncing labels..." +while IFS=';' read -r name color desc; do + [ -z "$name" ] && continue + "$GH" label create "$name" --repo "$REPO" --color "$color" --description "$desc" --force 2>/dev/null +done <<'EOF' +bug;d73a4a;Something isn't working +documentation;0075ca;Improvements or additions to documentation +duplicate;cfd3d7;This issue or pull request already exists +enhancement;a2eeef;New feature or request +good first issue;7057ff;Good for newcomers +help wanted;008672;Extra attention is needed +invalid;e4e669;This doesn't seem right +question;37326e;Further information is requested +wontfix;ffffff;This will not be worked on +chore;ededed;A routine task or common potentially re-occurring task +feature;a2eeef;New feature or request +go;16e2e2;Pull requests that update go code +ok-to-helm;0e8a16;PR is allowed to build an publish helm chart +dependencies;0366d6;Pull requests that update a dependency file +github-actions;80c4c6;PR created via GitHub action +help-wanted;811857;Extra attention is needed +good-first-issue;7057ff;Good for newcomers +needs-triage;eab668;Issue that has not been reviewed +ok-to-image;0e8a16;PR is allowed to run container build +ok-to-test;0e8a16;PR is allowed to be tested +spike;b23adb;A task to research a question and resolve problems +EOF + +echo " Configuring merge strategy..." +"$GH" api "repos/$REPO" -X PATCH \ + -f allow_merge_commit="$REPO_ALLOW_MERGE_COMMIT" \ + -f allow_squash_merge="$REPO_ALLOW_SQUASH_MERGE" \ + -f allow_rebase_merge="$REPO_ALLOW_REBASE_MERGE" \ + -f delete_branch_on_merge=true \ + -f allow_auto_merge=true >/dev/null + +echo " Enabling secret scanning..." +"$GH" api "repos/$REPO" -X PATCH \ + --input <(echo '{"security_and_analysis":{"secret_scanning":{"status":"enabled"}}}') >/dev/null + +RULESET_JSON=$( + # shellcheck disable=SC2016 # $variables belong to jq, not the shell + "$JQ" -cn \ + --argjson branches "$REPO_RULESET_BRANCHES_EFFECTIVE" \ + --argjson checks "$REPO_STATUS_CHECKS_EFFECTIVE" \ + --argjson approvals "$REPO_REQUIRED_APPROVING_REVIEW_COUNT" \ + --argjson codeOwner "$REPO_REQUIRE_CODE_OWNER_REVIEW" \ + --argjson adminBypass "$REPO_ADMIN_BYPASS" \ + --argjson upToDate "$REPO_REQUIRE_BRANCH_UP_TO_DATE" \ + '{ name: "protect-main", target: "branch", enforcement: "active", + conditions: { ref_name: { include: $branches, exclude: [] } }, + rules: ([ + { type: "deletion" }, + { type: "non_fast_forward" }, + { type: "creation" }, + { type: "required_signatures" }, + { type: "pull_request", parameters: { + required_approving_review_count: $approvals, + dismiss_stale_reviews_on_push: true, + required_reviewers: [], + require_code_owner_review: $codeOwner, + require_last_push_approval: false, + required_review_thread_resolution: true, + allowed_merge_methods: ["squash", "rebase", "merge"] + } } + ] + (if ($checks | length > 0) then + [{ type: "required_status_checks", parameters: { + strict_required_status_checks_policy: $upToDate, + required_status_checks: [$checks | .[] | { context: . }] + } }] else [] end)), + bypass_actors: (if $adminBypass then [{ actor_type: "OrganizationAdmin", bypass_mode: "always" }] else [] end) + }' +) + +echo " Configuring branch protection ruleset..." +echo " branches: $REPO_RULESET_BRANCHES_EFFECTIVE" +echo " approvals: $REPO_REQUIRED_APPROVING_REVIEW_COUNT" +echo " code owner review: $REPO_REQUIRE_CODE_OWNER_REVIEW" +echo " branch up-to-date: $REPO_REQUIRE_BRANCH_UP_TO_DATE" +echo " required status checks: $REPO_STATUS_CHECKS_EFFECTIVE" +echo " admin bypass: $REPO_ADMIN_BYPASS" + +existing=$("$GH" api "repos/$REPO/rulesets" -q '.[] | select(.name=="protect-main") | .id' 2>/dev/null || true) +if [ -n "$existing" ]; then + "$GH" api "repos/$REPO/rulesets/$existing" -X PUT --input <(echo "$RULESET_JSON") >/dev/null + echo " Updated existing ruleset (id: $existing)" +else + "$GH" api "repos/$REPO/rulesets" -X POST --input <(echo "$RULESET_JSON") >/dev/null + echo " Created new ruleset" +fi + +echo " Installing update-action-pins workflow..." +mkdir -p .github/workflows +curl --fail -sSL \ + "https://raw.githubusercontent.com/opendefensecloud/dev-kit/$DEV_KIT_VERSION/.github/workflows/update-action-pins.yml" \ + -o .github/workflows/update-action-pins.yml +echo " Wrote .github/workflows/update-action-pins.yml" + +echo "Done."