mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 21:26:44 +00:00
The wait for fork CI images never found the privileged workflow run it watches. It selected a run only when the run's pull_requests array held the PR number, and for a run whose head repository is a fork the API returns that array empty. The job only runs for fork PRs, so the lookup matched in no case it exists for. The early exit for a failed privileged run was therefore unreachable. When that run failed, the job kept polling until its timeout of 1980 attempts and held a runner for about five and a half hours. The lookup now asks the API for the runs of the PR head commit and selects by head_sha. That also replaces a paginated listing of every run of the workflow with one small request per attempt. Without a known head commit the match by PR number stays. Refs #784 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.files import PROJECT_ROOT
|
|
|
|
SCRIPT = PROJECT_ROOT / "scripts" / "meta" / "wait" / "images.sh"
|
|
HEAD_SHA = "5b002678df222f1095fae2f42e3bb10964adb8ef"
|
|
STUBS = {
|
|
"docker": '#!/usr/bin/env bash\n[[ -e "${STUB_DIR}/images-ready" ]]\n',
|
|
"gh": (
|
|
"#!/usr/bin/env bash\n"
|
|
'printf "gh %s\\n" "$*" >&2\n'
|
|
'if [[ -n "${STUB_IMAGES_APPEAR:-}" ]]; then\n'
|
|
'\ttouch "${STUB_DIR}/images-ready"\n'
|
|
"fi\n"
|
|
'if [[ -e "${STUB_DIR}/gh-response" ]]; then\n'
|
|
'\tcat "${STUB_DIR}/gh-response"\n'
|
|
"\texit 0\n"
|
|
"fi\n"
|
|
'echo "HTTP 504" >&2\n'
|
|
"exit 1\n"
|
|
),
|
|
}
|
|
|
|
|
|
def wait_for_images(
|
|
tmp: str, runs: list[dict] | None = None, **overrides: str
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run the wait script against stubbed ``docker`` and ``gh``.
|
|
|
|
Args:
|
|
tmp: directory that holds the stubs and their state.
|
|
runs: workflow runs ``gh`` answers with; without them every lookup fails.
|
|
overrides: environment entries layered over the defaults.
|
|
"""
|
|
stub_dir = Path(tmp)
|
|
for name, body in STUBS.items():
|
|
stub = stub_dir / name
|
|
stub.write_text(body)
|
|
stub.chmod(0o755)
|
|
if runs is not None:
|
|
(stub_dir / "gh-response").write_text(json.dumps({"workflow_runs": runs}))
|
|
inherited = {k: v for k, v in os.environ.items() if k != "BASH_ENV"}
|
|
env = {
|
|
**inherited,
|
|
"PATH": f"{stub_dir}{os.pathsep}{os.environ['PATH']}",
|
|
"STUB_DIR": str(stub_dir),
|
|
"GITHUB_REPOSITORY": "acme/widgets",
|
|
"GH_TOKEN": "unused",
|
|
"WORKFLOW_FILE": "entry.yml",
|
|
"TARGET_EVENT": "pull_request_target",
|
|
"PR_NUMBER": "1",
|
|
"PR_HEAD_SHA": HEAD_SHA,
|
|
"IMAGE_TAG": "ci-test",
|
|
"INFINITO_DISTROS": "debian",
|
|
"WAIT_ATTEMPTS": "100",
|
|
"WAIT_SLEEP_SECONDS": "0",
|
|
**overrides,
|
|
}
|
|
return subprocess.run(
|
|
["bash", str(SCRIPT)],
|
|
cwd=PROJECT_ROOT,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
class TestWaitForImages(unittest.TestCase):
|
|
def test_failed_lookup_does_not_end_the_wait(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, STUB_IMAGES_APPEAR="true")
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"Lookup of the privileged workflow run failed (1/30)", result.stdout
|
|
)
|
|
self.assertIn("All required CI images are available.", result.stdout)
|
|
|
|
def test_lookup_that_keeps_failing_ends_the_wait(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp)
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn("failed 30 times in a row", result.stderr)
|
|
|
|
def test_failed_privileged_run_of_a_fork_ends_the_wait(self) -> None:
|
|
fork_run = {
|
|
"id": 7,
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"created_at": "2026-10-04T12:41:18Z",
|
|
"head_sha": HEAD_SHA,
|
|
"pull_requests": [],
|
|
}
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, runs=[fork_run])
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"Privileged run 7 finished with conclusion=failure", result.stderr
|
|
)
|
|
self.assertIn(f"head_sha={HEAD_SHA}", result.stderr)
|
|
|
|
def test_run_of_another_head_is_ignored(self) -> None:
|
|
other_run = {
|
|
"id": 8,
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"created_at": "2026-10-04T12:41:18Z",
|
|
"head_sha": "0" * 40,
|
|
"pull_requests": [],
|
|
}
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, runs=[other_run], WAIT_ATTEMPTS="2")
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"No matching privileged workflow run found yet", result.stdout
|
|
)
|
|
self.assertIn("Timed out waiting for CI images", result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|