Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,23 @@ 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: 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 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 |
| `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

Expand Down
116 changes: 31 additions & 85 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -52,94 +52,40 @@ 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

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" }] \
}
# 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
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 ?= []
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..."; \
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 " 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
Expand Down
10 changes: 9 additions & 1 deletion docs/NEW_REPO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ 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.:

```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

Expand Down
146 changes: 146 additions & 0 deletions scripts/repo-settings.sh
Original file line number Diff line number Diff line change
@@ -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
}
Comment thread
olzemal marked this conversation as resolved.

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
Comment thread
olzemal marked this conversation as resolved.
echo " Wrote .github/workflows/update-action-pins.yml"

echo "Done."
Loading