Skip to content
Closed
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
14 changes: 11 additions & 3 deletions scripts/apply-rulesets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,17 @@ PAYLOAD
# Apply
# ---------------------------------------------------------------------------
info "Fetching existing rulesets for $ORG/$REPO ..."
if ! existing=$(gh api "repos/$ORG/$REPO/rulesets"); then
err "Failed to fetch existing rulesets for $ORG/$REPO — check GH_TOKEN has administration:read scope"
exit 1
if ! existing=$(gh api "repos/$ORG/$REPO/rulesets" 2>/dev/null); then
# In dry-run mode the fetch is best-effort: fall back to treating the repo as
# having no existing rulesets so the codified payloads can be previewed
# offline (without an admin token). Real runs must fail hard on a failed fetch.
if [[ "$DRY_RUN" = true ]]; then
skip "Could not fetch existing rulesets — previewing payloads as new (create)"
Comment on lines 179 to +185
existing='[]'
else
err "Failed to fetch existing rulesets for $ORG/$REPO — check GH_TOKEN has administration:read scope"
exit 1
fi
fi
Comment on lines +180 to 191

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the gh CLI is not installed or not available in the system's PATH, the command substitution gh api ... will fail with exit code 127. In non-dry-run mode, the script currently outputs a misleading error message suggesting that the GH_TOKEN lacks the administration:read scope, which can lead to confusing troubleshooting.

We can capture the exit status of the command substitution and provide a more accurate error message if gh is missing.

Suggested change
if ! existing=$(gh api "repos/$ORG/$REPO/rulesets" 2>/dev/null); then
# In dry-run mode the fetch is best-effort: fall back to treating the repo as
# having no existing rulesets so the codified payloads can be previewed
# offline (without an admin token). Real runs must fail hard on a failed fetch.
if [[ "$DRY_RUN" = true ]]; then
skip "Could not fetch existing rulesets — previewing payloads as new (create)"
existing='[]'
else
err "Failed to fetch existing rulesets for $ORG/$REPO — check GH_TOKEN has administration:read scope"
exit 1
fi
fi
if ! existing=$(gh api "repos/$ORG/$REPO/rulesets" 2>/dev/null); then
gh_err=$?
# In dry-run mode the fetch is best-effort: fall back to treating the repo as
# having no existing rulesets so the codified payloads can be previewed
# offline (without an admin token). Real runs must fail hard on a failed fetch.
if [[ "$DRY_RUN" = true ]]; then
skip "Could not fetch existing rulesets — previewing payloads as new (create)"
existing='[]'
else
if [[ $gh_err -eq 127 ]]; then
err "Failed to fetch existing rulesets: 'gh' CLI is not installed or not in PATH"
else
err "Failed to fetch existing rulesets for $ORG/$REPO — check GH_TOKEN has administration:read scope"
fi
exit 1
fi
fi


apply_ruleset() {
Expand Down
76 changes: 76 additions & 0 deletions tests/scripts/apply-rulesets.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Test: scripts/apply-rulesets.sh codifies the required pr-quality ruleset
# review parameters from the standard ruleset.
#
# The compliance standard requires the pr-quality ruleset to enforce
# require_code_owner_review=true (among other review requirements). This guards
# the codified payload against silent drift in the repo. Live GitHub-state
# convergence is a separate operation that requires an admin token.
#
# Runs the script in --dry-run --force mode so no real GitHub API calls are
# made and the repo-identity safety check is skipped. Asserts the dry-run
# output codifies each required pr-quality review parameter.
Comment on lines +5 to +12
#
# Run: bash tests/scripts/apply-rulesets.test.sh

set -uo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="${REPO_ROOT}/scripts/apply-rulesets.sh"

# Required pr-quality review parameters (needle → description).
REQUIRED_PR_QUALITY_PARAMS=(
'"require_code_owner_review": true|enforces require_code_owner_review=true'
'"required_approving_review_count": 1|enforces required_approving_review_count=1'
'"require_last_push_approval": true|enforces require_last_push_approval=true'
'"dismiss_stale_reviews_on_push": true|enforces dismiss_stale_reviews_on_push=true'
'"required_review_thread_resolution": true|enforces required_review_thread_resolution=true'
)

fail=0
pass_count=0

assert_contains() {
local needle="$1" desc="$2"
if printf '%s' "$output" | grep -qF -- "$needle"; then
echo "ok - ${desc}"
pass_count=$((pass_count + 1))
else
echo "not ok - ${desc} (output missing: ${needle})"
fail=1
fi
return 0
}

if [[ ! -x "$SCRIPT" && ! -f "$SCRIPT" ]]; then
echo "not ok - script not found at ${SCRIPT}"
exit 1
fi
Comment on lines +45 to +48

# Dummy token satisfies the GH_TOKEN guard; --force skips the repo-identity
# check; --dry-run prevents any real API call.
output="$(GH_TOKEN=dummy-token bash "$SCRIPT" --dry-run --force 2>&1)"
exit_code=$?

if [[ "$exit_code" -ne 0 ]]; then
echo "not ok - script exited non-zero (${exit_code}) in --dry-run --force mode"
echo "--- output ---"
printf '%s\n' "$output"
fail=1
fi

assert_contains '"name": "pr-quality"' "codifies the pr-quality ruleset"

for entry in "${REQUIRED_PR_QUALITY_PARAMS[@]}"; do
needle="${entry%%|*}"
desc="${entry##*|}"
assert_contains "$needle" "$desc"
done

echo "---"
if [[ "$fail" -eq 0 ]]; then
echo "PASS — ${pass_count} assertion(s) passed"
else
echo "FAIL — see assertions above"
fi
exit "$fail"
Loading