ci.gatekeeper: Recognize latest ci-devel run

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

Select the newest ci-devel workflow_dispatch run for the exact pull request tip instead of requiring a run link in the pull request description.

Use that run as alternate authorization and link it from the gatekeeper step summary. If no matching run exists, 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 13a51c7679
commit 1788576df6

View File

@@ -38,6 +38,8 @@ 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']}"
_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 +61,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 +186,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 +267,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 +290,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 +299,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 +310,31 @@ 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}")
def find_ci_devel_run(self, workflow_runs):
"""Find the latest ci-devel run for the commit being checked"""
run = max(
(run for run in workflow_runs
if run.get("path") == _CI_DEVEL_WORKFLOW_PATH and
run.get("event") == "workflow_dispatch" and
run.get("head_sha") == self.latest_commit_sha),
key=lambda run: run["id"],
default=None,
)
if run is None:
print(f"No ci-devel run found for {self.latest_commit_sha}; "
"using regular workflow runs.", file=sys.stderr)
return None
print(f"Using latest ci-devel run {run['html_url']}", file=sys.stderr)
return run
def check_required_labels(self, pull_request=None):
"""
Check if all expected labels are present
@@ -308,14 +349,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 +367,15 @@ 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()
workflow_runs = self.get_workflow_runs(1)
self.ci_devel_run = self.find_ci_devel_run(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__":