ci: add rebase-required mode to check-maintenance action (#23109)
Co-authored-by: Alison Shao <alisonshao@radixark.ai>
This commit is contained in:
co-authored by
Alison Shao
parent
f75a8b6220
commit
cdc4078815
@@ -150,5 +150,18 @@ When the CI is unhealthy (e.g., the scheduled pr-test on `main` is broken for co
|
||||
|
||||
Maintenance mode ends when `pr-test.yml` is all green on `main` and the issue is closed.
|
||||
|
||||
### Rebase-Required Mode
|
||||
When a major update lands on `main` and all open PRs must rebase before CI can run (without fully pausing CI), add a line of the form `MIN_BASE_SHA: <sha>` to the body of issue #21065. **The rebase check is enforced regardless of whether the issue is open or closed** — you do not need to enter full maintenance mode (open the issue) to use this gate; just editing the body to include the directive is enough. While the directive is present:
|
||||
- CI is allowed to run only for PRs whose branch already contains `<sha>` (GitHub compare API status `ahead` or `identical` — i.e., the PR has `<sha>` in its history).
|
||||
- PRs that are `behind` or `diverged` from `<sha>` are blocked with a "rebase required" error until they rebase onto the latest `main`.
|
||||
- The `bypass-maintenance` label still bypasses this check for CI-fix PRs.
|
||||
|
||||
Notes:
|
||||
- Only the **first** `MIN_BASE_SHA:` line in the issue body is read.
|
||||
- The SHA must be 7-40 hex characters; malformed values are ignored (with a warning in the job summary).
|
||||
- Avoid pasting the directive inside a fenced code block in the issue body — the parser does not skip code fences and may match example snippets.
|
||||
|
||||
Remove the directive from the issue body to lift the rebase requirement (closing the issue does NOT lift it on its own).
|
||||
|
||||
## Suspending Permissions
|
||||
If a Merge Oncall bypasses checks to merge a PR that breaks the `main` branch, merges a non-CI-fix PR during CI Maintenance Mode, or repeatedly breaks the CI due to various reasons, their privileges will be suspended for at least two days, depending on the severity of the incident.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Check Maintenance Mode
|
||||
description: Blocks CI when maintenance mode is active (issue #21065 is open), unless the PR has the bypass-maintenance label, or env PR_TEST_BYPASS_MAINTENANCE_ON_MAIN=true (PR Test workflow on main only). Merging non-CI-fix PRs is prohibited during maintenance mode; in severe cases, merge permissions may be revoked.
|
||||
description: Blocks CI in two independent modes driven by issue #21065. (1) Full-pause: when the issue is open. (2) Rebase-required: whenever the issue body contains a `MIN_BASE_SHA: <sha>` directive — enforced regardless of whether the issue is open or closed, so maintainers can require all PRs to rebase past a specific commit without having to open the maintenance issue. Both modes are bypassed by the `bypass-maintenance` label on the PR, or by env PR_TEST_BYPASS_MAINTENANCE_ON_MAIN=true (PR Test workflow on main only). Merging non-CI-fix PRs is prohibited during full-pause; in severe cases, merge permissions may be revoked.
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
@@ -18,6 +18,7 @@ runs:
|
||||
MAINTENANCE_ISSUE=21065
|
||||
REPO="${{ github.repository }}"
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
# PR Test workflow only: scheduled runs and runs on main (dispatch / workflow_call) set this env
|
||||
if [[ "${PR_TEST_BYPASS_MAINTENANCE_ON_MAIN:-}" == "true" ]]; then
|
||||
@@ -25,23 +26,82 @@ runs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if maintenance issue is open (fail-open: if API errors, allow CI to proceed)
|
||||
ISSUE_STATE=$(gh issue view "$MAINTENANCE_ISSUE" --repo "$REPO" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN")
|
||||
# Fetch issue state and body (fail-open: if API errors, allow CI to proceed).
|
||||
ISSUE_JSON=$(gh issue view "$MAINTENANCE_ISSUE" --repo "$REPO" --json state,body 2>/dev/null || echo "")
|
||||
ISSUE_STATE=$(printf '%s' "$ISSUE_JSON" | jq -r '.state // "UNKNOWN"' 2>/dev/null || echo "UNKNOWN")
|
||||
ISSUE_BODY=$(printf '%s' "$ISSUE_JSON" | jq -r '.body // ""' 2>/dev/null || echo "")
|
||||
|
||||
if [[ "$ISSUE_STATE" != "OPEN" ]]; then
|
||||
echo "✅ Maintenance mode is OFF. Proceeding with CI."
|
||||
# Parse optional `MIN_BASE_SHA: <sha>` directive from the issue body
|
||||
# (first occurrence wins). Whenever this directive is present, the
|
||||
# rebase check is enforced regardless of whether the issue is open
|
||||
# or closed — so maintainers can require all PRs to rebase past a
|
||||
# specific commit without having to open the maintenance issue.
|
||||
MIN_BASE_SHA=$(printf '%s' "$ISSUE_BODY" | tr -d '\r' | grep -iE '^[[:space:]]*`?MIN_BASE_SHA`?[[:space:]]*[:=]' | head -n1 | sed -E 's/.*[:=][[:space:]]*//; s/`//g' | awk '{print $1}')
|
||||
if [[ -n "$MIN_BASE_SHA" ]] && ! [[ "$MIN_BASE_SHA" =~ ^[a-fA-F0-9]{7,40}$ ]]; then
|
||||
WARN="⚠️ Ignoring malformed MIN_BASE_SHA directive in issue #$MAINTENANCE_ISSUE: '$MIN_BASE_SHA' (must be 7-40 hex chars)"
|
||||
echo "$WARN"
|
||||
echo "$WARN" >> "$GITHUB_STEP_SUMMARY"
|
||||
MIN_BASE_SHA=""
|
||||
fi
|
||||
|
||||
# If neither gate is active (no MIN_BASE_SHA, issue not open), nothing to do.
|
||||
if [[ -z "$MIN_BASE_SHA" && "$ISSUE_STATE" != "OPEN" ]]; then
|
||||
echo "✅ Maintenance mode is OFF and no MIN_BASE_SHA directive. Proceeding with CI."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For PRs, check if bypass-maintenance label is present
|
||||
# bypass-maintenance label bypasses both gates.
|
||||
if [[ -n "$PR_NUMBER" ]]; then
|
||||
HAS_BYPASS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | map(select(. == "bypass-maintenance")) | length' 2>/dev/null || echo "0")
|
||||
if [[ "$HAS_BYPASS" -gt 0 ]]; then
|
||||
echo "✅ PR #$PR_NUMBER has 'bypass-maintenance' label. Bypassing maintenance mode."
|
||||
echo "✅ PR #$PR_NUMBER has 'bypass-maintenance' label. Bypassing maintenance + rebase checks."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Rebase-required gate (independent of issue open/closed state).
|
||||
if [[ -n "$MIN_BASE_SHA" ]]; then
|
||||
if [[ -z "$PR_NUMBER" || -z "$PR_HEAD_SHA" ]]; then
|
||||
echo "✅ Not a PR context; skipping rebase check."
|
||||
else
|
||||
# Use GitHub compare API: status is "ahead"/"identical" when MIN_BASE_SHA is reachable from PR head.
|
||||
# On 4xx (e.g., nonexistent SHA) gh api prints the error JSON to stdout before exiting non-zero,
|
||||
# so keep the `|| COMPARE_STATUS=UNKNOWN` outside the subshell — otherwise --jq '.status' extracts
|
||||
# the HTTP status code ("404") from the error body and concatenates with "UNKNOWN", bypassing the
|
||||
# fail-open branch and incorrectly blocking CI.
|
||||
COMPARE_STATUS=$(gh api "repos/$REPO/compare/$MIN_BASE_SHA...$PR_HEAD_SHA" --jq '.status' 2>/dev/null) \
|
||||
|| COMPARE_STATUS="UNKNOWN"
|
||||
COMPARE_STATUS="${COMPARE_STATUS:-UNKNOWN}"
|
||||
|
||||
case "$COMPARE_STATUS" in
|
||||
ahead|identical)
|
||||
echo "✅ PR #$PR_NUMBER contains required base ${MIN_BASE_SHA:0:12} ($COMPARE_STATUS)."
|
||||
;;
|
||||
UNKNOWN)
|
||||
echo "⚠️ Could not determine rebase status via GitHub API; fail-open, allowing rebase check to pass."
|
||||
;;
|
||||
*)
|
||||
MSG=$(printf "%s\n" \
|
||||
"## ⚠️ Rebase Required Before CI Can Run" \
|
||||
"A major update has landed on \`main\`. All PRs must rebase onto the latest \`main\` before CI will run." \
|
||||
"Required base commit: \`${MIN_BASE_SHA:0:12}\` (your PR is \`$COMPARE_STATUS\` relative to this commit)." \
|
||||
"" \
|
||||
"What should you do?" \
|
||||
"- Rebase your branch onto the latest \`main\` and push again" \
|
||||
"- Follow CI Maintenance Mode issue: https://github.com/$REPO/issues/$MAINTENANCE_ISSUE for context" \
|
||||
"- CI-fix PRs may request the \`bypass-maintenance\` label to skip this check")
|
||||
echo "$MSG" >> "$GITHUB_STEP_SUMMARY"
|
||||
while IFS= read -r line; do
|
||||
echo "::error::$line"
|
||||
done <<< "$MSG"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Full-pause maintenance gate (only when issue is open).
|
||||
if [[ "$ISSUE_STATE" == "OPEN" ]]; then
|
||||
MSG=$(printf "%s\n" \
|
||||
"## ⚠️ CI Maintenance Mode is Active" \
|
||||
"The CI infrastructure is currently under maintenance." \
|
||||
@@ -59,5 +119,7 @@ runs:
|
||||
while IFS= read -r line; do
|
||||
echo "::error::$line"
|
||||
done <<< "$MSG"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Rebase check passed; full-pause not active. Proceeding with CI."
|
||||
|
||||
Reference in New Issue
Block a user