Files
rustfs/scripts/ci_merge_queue.py
T
Chris An 4fcbf60f10 ci: unify validation planning, artifacts and suite orchestration (#9059)
* ci: unify validation planning, artifacts and suite orchestration

* fix(ci): preserve JSON diagnostics for release checks

* fix(ci): keep manual attempts without plans visible

* fix(ci): distinguish nextest setup scripts from test results

* ci: reuse extracted archives and consolidate duplicate checks
2026-10-30 02:02:38 +08:00

100 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Prepare or compare merge-queue ruleset payloads without changing GitHub."""
import argparse
import copy
import hashlib
import json
from pathlib import Path
import sys
REQUIRED_CHECKS = {"Test and Lint", "Quick Checks", "CLA Check"}
WRITABLE_FIELDS = ("name", "target", "enforcement", "bypass_actors", "conditions", "rules")
QUEUE_RULE = {
"type": "merge_queue",
"parameters": {
"check_response_timeout_minutes": 180,
"grouping_strategy": "ALLGREEN",
"max_entries_to_build": 2,
"max_entries_to_merge": 2,
"merge_method": "SQUASH",
"min_entries_to_merge": 1,
"min_entries_to_merge_wait_minutes": 5,
},
}
def writable(snapshot):
if not isinstance(snapshot, dict) or snapshot.get("target") != "branch" or snapshot.get("enforcement") != "active":
raise ValueError("an active branch ruleset snapshot is required")
result = {key: copy.deepcopy(snapshot[key]) for key in WRITABLE_FIELDS if key in snapshot}
rules = result.get("rules", [])
required = [rule for rule in rules if rule.get("type") == "required_status_checks"]
if len(required) != 1:
raise ValueError("exactly one required-check rule is required")
contexts = required[0].get("parameters", {}).get("required_status_checks", [])
if len(contexts) != len(REQUIRED_CHECKS) or {item.get("context") for item in contexts} != REQUIRED_CHECKS:
raise ValueError("required checks changed; reconcile the workflow before preparing a switch")
if any(item.get("integration_id") != 15368 for item in contexts):
raise ValueError("required checks must be owned by GitHub Actions")
reviews = [rule for rule in rules if rule.get("type") == "pull_request"]
if len(reviews) != 1 or reviews[0].get("parameters", {}).get("required_approving_review_count", 0) < 1:
raise ValueError("queue preparation requires the existing review boundary")
if "squash" not in reviews[0]["parameters"].get("allowed_merge_methods", []):
raise ValueError("ruleset does not permit the proposed squash method")
includes = result.get("conditions", {}).get("ref_name", {}).get("include", [])
if includes not in (["~DEFAULT_BRANCH"], ["refs/heads/main"]):
raise ValueError("queue preparation is restricted to the main ruleset")
return result
def fingerprint(value):
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def prepare(snapshot):
before = writable(snapshot)
if any(rule.get("type") == "merge_queue" for rule in before["rules"]):
raise ValueError("merge queue already configured; compare the existing policy instead")
after = copy.deepcopy(before)
after["rules"].append(copy.deepcopy(QUEUE_RULE))
return {"schema": 1, "ruleset_id": snapshot.get("id"), "before_sha256": fingerprint(before),
"status": "prepared-not-applied", "before": before, "after": after}
def compare(prepared, snapshot, phase):
if prepared.get("schema") != 1 or phase not in ("before", "after"):
raise ValueError("invalid switch record or phase")
if prepared.get("ruleset_id") != snapshot.get("id"):
raise ValueError("readback belongs to a different ruleset")
if fingerprint(prepared["before"]) != prepared.get("before_sha256"):
raise ValueError("original snapshot was modified")
expected = prepare(dict(prepared["before"], id=prepared["ruleset_id"]))
if prepared != expected:
raise ValueError("prepared change differs from the bounded queue policy")
if writable(snapshot) != prepared[phase]:
raise ValueError(f"live ruleset differs from the {phase} snapshot")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("snapshot", type=Path)
parser.add_argument("--prepared", type=Path)
parser.add_argument("--phase", choices=("before", "after"), default="before")
args = parser.parse_args()
try:
snapshot = json.loads(args.snapshot.read_text())
if args.prepared:
compare(json.loads(args.prepared.read_text()), snapshot, args.phase)
print(f"Ruleset {args.phase} readback matches")
else:
print(json.dumps(prepare(snapshot), indent=2))
except (OSError, ValueError, KeyError, TypeError) as error:
print(f"merge queue preflight: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())