ci.gatekeeper: Recognize linked ci-devel runs

Gatekeeper requires ok-to-test before checking required jobs, even when a maintainer has dispatched ci-devel for the pull request tip.

Recognize the first linked ci-devel run only when its workflow, event, and commit match.

Use that run as alternate authorization and link it from the gatekeeper step summary. Invalid links retain the regular path.

Generated-By: GitHub Copilot
Signed-off-by: Aurélien Bombo <abombo@microsoft.com>
This commit is contained in:
Aurélien Bombo
2026-07-24 15:31:54 -05:00
parent 4dd74798c0
commit 46ae792ff2

View File

@@ -38,6 +38,11 @@ if os.environ.get("GITHUB_TOKEN"):
_GH_HEADERS["Authorization"] = f"Bearer {os.environ['GITHUB_TOKEN']}"
_GH_API_URL = f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}"
_GH_RUNS_URL = f"{_GH_API_URL}/actions/runs"
_GH_WEB_URL = f"https://github.com/{os.environ['GITHUB_REPOSITORY']}"
_GH_RUN_URL_RE = re.compile(
rf"{re.escape(_GH_WEB_URL)}/actions/runs/(\d+)"
)
_CI_DEVEL_WORKFLOW_PATH = ".github/workflows/ci-devel.yaml"
_GH_SUMMARY_URL = (
f"{os.environ.get('GITHUB_SERVER_URL')}/"
f"{os.environ.get('GITHUB_REPOSITORY')}/actions/runs/"
@@ -59,6 +64,7 @@ class Checker:
def __init__(self):
self.latest_commit_sha = os.getenv("COMMIT_HASH")
self.pr_number = os.getenv("GH_PR_NUMBER")
self.ci_devel_run = None
required_labels = os.getenv("REQUIRED_LABELS")
if required_labels:
self.required_labels = set(required_labels.split(";"))
@@ -183,6 +189,14 @@ class Checker:
failing = []
running = []
if self.ci_devel_run:
run_id = self.ci_devel_run["id"]
run_url = self.ci_devel_run.get(
"html_url", f"{_GH_WEB_URL}/actions/runs/{run_id}"
)
lines.append("## ci-devel run\n")
lines.append(f"[Scanned ci-devel run {run_id}]({run_url})\n")
for name, job in self.results.items():
status = self._job_status(job)
url = job.get("html_url", "")
@@ -256,16 +270,21 @@ class Checker:
return self.paginated_fetch(
url, "jobs", f"get_jobs_for_workflow_run__{run_id}")
def check_workflow_runs_status(self, attempt):
def get_workflow_runs(self, attempt):
"""Get workflow runs for the commit being checked"""
return self.paginated_fetch(
_GH_RUNS_URL, "workflow_runs",
f"check_workflow_runs_status_{attempt}",
{"head_sha": self.latest_commit_sha})
def check_workflow_runs_status(self, attempt, workflow_runs=None):
"""
Checks if all required jobs passed
:returns: 0 - all passing; 1 - any failure; 127 some jobs running
"""
workflow_runs = self.paginated_fetch(
_GH_RUNS_URL, "workflow_runs",
f"check_workflow_runs_status_{attempt}",
{"head_sha": self.latest_commit_sha})
if workflow_runs is None:
workflow_runs = self.get_workflow_runs(attempt)
for run in workflow_runs:
jobs = self.get_jobs_for_workflow_run(run["id"])
for job in jobs:
@@ -274,7 +293,7 @@ class Checker:
self.write_step_summary()
return self.status()
def wait_for_required_tests(self):
def wait_for_required_tests(self, workflow_runs=None):
"""
Wait for all required tests to pass or failfast
@@ -283,7 +302,8 @@ class Checker:
i = 0
while True:
i += 1
ret = self.check_workflow_runs_status(i)
ret = self.check_workflow_runs_status(i, workflow_runs)
workflow_runs = None
if ret == RUNNING:
running_jobs = len([name
for name, job in self.results.items()
@@ -293,7 +313,41 @@ class Checker:
continue
return ret
def check_required_labels(self):
def get_pull_request(self):
"""Get pull request metadata from its issue endpoint"""
return self.fetch_json_from_url(
f"{_GH_API_URL}/issues/{self.pr_number}",
f"get_pull_request__{self.pr_number}")
@staticmethod
def get_linked_run_id(body):
"""Get the first same-repository Actions run linked in a PR body"""
match = _GH_RUN_URL_RE.search(body or "")
return int(match.group(1)) if match else None
def find_ci_devel_run(self, pull_request, workflow_runs):
"""Find a linked ci-devel run for the commit being checked"""
run_id = self.get_linked_run_id(pull_request.get("body"))
if run_id is None:
return None
for run in workflow_runs:
if run["id"] != run_id:
continue
if (run.get("path") == _CI_DEVEL_WORKFLOW_PATH and
run.get("event") == "workflow_dispatch" and
run.get("head_sha") == self.latest_commit_sha):
print(f"Using linked ci-devel run {run['html_url']}",
file=sys.stderr)
return run
break
print(f"Linked Actions run {run_id} is not a ci-devel run for "
f"{self.latest_commit_sha}; using regular workflow runs.",
file=sys.stderr)
return None
def check_required_labels(self, pull_request=None):
"""
Check if all expected labels are present
@@ -308,14 +362,10 @@ class Checker:
f"required-labels-check ({self.required_labels})")
return True
response = requests.get(
f"{_GH_API_URL}/issues/{self.pr_number}",
headers=_GH_HEADERS,
timeout=60
)
response.raise_for_status()
labels = set(_["name"] for _ in response.json()["labels"])
if self.required_labels.issubset(labels):
if pull_request is None:
pull_request = self.get_pull_request()
labels = set(_["name"] for _ in pull_request["labels"])
if self.required_labels.issubset(labels) or self.ci_devel_run:
return True
print(f"To run all required tests the PR{self.pr_number} must have "
f"{', '.join(self.required_labels.difference(labels))} labels "
@@ -330,9 +380,17 @@ class Checker:
"""
print(f"Gatekeeper for project={os.environ['GITHUB_REPOSITORY']} and "
f"SHA={self.latest_commit_sha} PR={self.pr_number}")
if not self.check_required_labels():
pull_request = None
workflow_runs = None
if self.pr_number:
pull_request = self.get_pull_request()
if self.get_linked_run_id(pull_request.get("body")) is not None:
workflow_runs = self.get_workflow_runs(1)
self.ci_devel_run = self.find_ci_devel_run(
pull_request, workflow_runs)
if not self.check_required_labels(pull_request):
sys.exit(2)
sys.exit(self.wait_for_required_tests())
sys.exit(self.wait_for_required_tests(workflow_runs))
if __name__ == "__main__":