diff --git a/README.md b/README.md index 101692a..4400bbd 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ The included `common.mk` provides: | `shellcheck` | Run shellcheck on shell scripts | | `scan` | Scan for vulnerabilities using osv-scanner | | `setup-local-cluster` | Create a Kind cluster for local development | +| `repo-settings` | Reconcile GitHub repository settings | ### Variables @@ -87,6 +88,17 @@ cobra: $(COBRA) $(COBRA) help ``` +### Repository settings + +Run `make repo-settings` to reconcile your GitHub repository with the organization's standard configuration. This target is idempotent and requires the `gh` CLI to be authenticated. + +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) + ### Default git hooks The dev shell installs the following git hooks automatically: diff --git a/common.mk b/common.mk index bd5cdc2..e42e7a8 100644 --- a/common.mk +++ b/common.mk @@ -26,6 +26,9 @@ KUBECTL ?= kubectl SHELLCHECK ?= shellcheck YQ ?= yq +# External prerequisites (not managed by flake.nix or tools.lock) +GH ?= gh + OS := $(or $(shell $(GO) env GOOS 2>/dev/null), \ $(shell uname -s | tr '[:upper:]' '[:lower:]')) ARCH := $(or $(shell $(GO) env GOARCH 2>/dev/null), \ @@ -44,6 +47,91 @@ OPENAPI_GEN ?= $(LOCALGOBIN)/openapi-gen OSV_SCANNER ?= $(LOCALGOBIN)/osv-scanner 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 +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 +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" }] \ +} + +.PHONY: repo-settings +repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, branch protection, security) + @REPO=$$($(GH) repo view --json nameWithOwner -q .nameWithOwner) || { echo "error: not a GitHub repository or gh not authenticated"; 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..."; \ + 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; \ + echo " Updated existing ruleset (id: $$existing)"; \ + else \ + $(GH) api "repos/$$REPO/rulesets" -X POST --input <(echo '$(REPO_RULESET)') > /dev/null; \ + echo " Created new ruleset"; \ + fi; \ + \ + echo "Done." + ##@ General # The help target prints out all targets with their descriptions organized