From 5c43b3da2e33f4b7df58b6ffb536ab2422014b0f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 18 Jul 2026 19:34:31 +0800 Subject: [PATCH] perf(cd): skip redundant mirror and bootstrap work --- .gitea/workflows/cd.yaml | 15 +-- scripts/ci/bootstrap-host-runner-tools.sh | 43 +++++++++ .../tests/test_bootstrap_host_runner_tools.py | 96 +++++++++++++++++++ .../runtime_image_mirror_controller.py | 62 ++++++++---- .../test_runtime_image_mirror_controller.py | 83 +++++++++++++++- 5 files changed, 269 insertions(+), 30 deletions(-) create mode 100755 scripts/ci/bootstrap-host-runner-tools.sh create mode 100644 scripts/ci/tests/test_bootstrap_host_runner_tools.py diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index ac6b210ae..b80da6203 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -133,10 +133,7 @@ jobs: # 2026-06-28 Codex: awoooi-non110-host maps to the dedicated # non-110 runner lane. Bootstrap tools defensively because host # runners can start without the CI toolchain preinstalled. - run: | - if command -v apk >/dev/null 2>&1; then - apk add --no-cache nodejs npm git curl bash coreutils python3 openssh-client docker-cli docker-cli-buildx - fi + run: bash scripts/ci/bootstrap-host-runner-tools.sh - name: Checkout source from Gitea env: @@ -1454,10 +1451,7 @@ jobs: - name: Bootstrap Host Runner Tools # 2026-05-05 Codex: keep the host-mode runner self-healing before # actions/checkout@v4 and Telegram failure notifications run. - run: | - if command -v apk >/dev/null 2>&1; then - apk add --no-cache nodejs npm git curl bash coreutils python3 openssh-client docker-cli docker-cli-buildx - fi + run: bash scripts/ci/bootstrap-host-runner-tools.sh - name: Checkout source from Gitea env: @@ -5226,10 +5220,7 @@ jobs: - name: Bootstrap Host Runner Tools # 2026-05-05 Codex: post-deploy also uses checkout and curl-based # notifications, so it needs the same runner bootstrap as earlier jobs. - run: | - if command -v apk >/dev/null 2>&1; then - apk add --no-cache nodejs npm git curl bash coreutils python3 openssh-client docker-cli docker-cli-buildx - fi + run: bash scripts/ci/bootstrap-host-runner-tools.sh - name: Checkout source from Gitea env: diff --git a/scripts/ci/bootstrap-host-runner-tools.sh b/scripts/ci/bootstrap-host-runner-tools.sh new file mode 100755 index 000000000..686008f83 --- /dev/null +++ b/scripts/ci/bootstrap-host-runner-tools.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +required_commands=(node npm git curl bash timeout python3 ssh docker) +missing_commands=() + +for command_name in "${required_commands[@]}"; do + if ! command -v "${command_name}" >/dev/null 2>&1; then + missing_commands+=("${command_name}") + fi +done + +if command -v docker >/dev/null 2>&1 && \ + ! docker buildx version >/dev/null 2>&1; then + missing_commands+=(docker-buildx) +fi + +if [ "${#missing_commands[@]}" -eq 0 ]; then + echo "host_runner_tools_ready=1 bootstrap_install_performed=0" + exit 0 +fi + +printf 'host_runner_tools_missing=%s\n' "$(IFS=,; echo "${missing_commands[*]}")" +if ! command -v apk >/dev/null 2>&1; then + echo "BLOCKER host_runner_tools_missing_and_apk_unavailable" + exit 1 +fi + +apk add --no-cache \ + nodejs npm git curl bash coreutils python3 openssh-client docker-cli docker-cli-buildx + +for command_name in "${required_commands[@]}"; do + command -v "${command_name}" >/dev/null 2>&1 || { + echo "BLOCKER host_runner_tool_install_failed command=${command_name}" + exit 1 + } +done +docker buildx version >/dev/null 2>&1 || { + echo "BLOCKER host_runner_tool_install_failed command=docker-buildx" + exit 1 +} + +echo "host_runner_tools_ready=1 bootstrap_install_performed=1" diff --git a/scripts/ci/tests/test_bootstrap_host_runner_tools.py b/scripts/ci/tests/test_bootstrap_host_runner_tools.py new file mode 100644 index 000000000..2fef4c971 --- /dev/null +++ b/scripts/ci/tests/test_bootstrap_host_runner_tools.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import os +import subprocess +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +SCRIPT = ROOT / "scripts/ci/bootstrap-host-runner-tools.sh" +REQUIRED = ("node", "npm", "git", "curl", "bash", "timeout", "python3", "ssh") + + +def _write_command(path: Path, body: str = "exit 0\n") -> None: + path.write_text("#!/bin/sh\n" + body, encoding="utf-8") + path.chmod(0o755) + + +def _fake_tool_path(directory: Path, *, include_npm: bool) -> None: + for command in REQUIRED: + if command != "npm" or include_npm: + _write_command(directory / command) + _write_command(directory / "docker") + + +def _run(directory: Path) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["PATH"] = str(directory) + return subprocess.run( + ["/bin/bash", str(SCRIPT)], + check=False, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + + +def test_ready_runner_skips_package_install() -> None: + with tempfile.TemporaryDirectory() as temp: + directory = Path(temp) + _fake_tool_path(directory, include_npm=True) + + completed = _run(directory) + + assert completed.returncode == 0 + assert "host_runner_tools_ready=1 bootstrap_install_performed=0" in completed.stdout + + +def test_missing_tool_without_apk_fails_closed() -> None: + with tempfile.TemporaryDirectory() as temp: + directory = Path(temp) + _fake_tool_path(directory, include_npm=False) + + completed = _run(directory) + + assert completed.returncode == 1 + assert "host_runner_tools_missing=npm" in completed.stdout + assert "BLOCKER host_runner_tools_missing_and_apk_unavailable" in completed.stdout + + +def test_missing_tool_uses_apk_once_and_revalidates() -> None: + with tempfile.TemporaryDirectory() as temp: + directory = Path(temp) + _fake_tool_path(directory, include_npm=False) + apk_log = directory / "apk.log" + _write_command( + directory / "apk", + 'printf "%s\\n" "$*" > "$APK_LOG"\n' + 'printf "#!/bin/sh\\nexit 0\\n" > "$FAKE_BIN/npm"\n' + '/bin/chmod +x "$FAKE_BIN/npm"\n', + ) + env = os.environ.copy() + env.update( + { + "PATH": str(directory), + "APK_LOG": str(apk_log), + "FAKE_BIN": str(directory), + } + ) + + completed = subprocess.run( + ["/bin/bash", str(SCRIPT)], + check=False, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + + install_args = apk_log.read_text(encoding="utf-8") + + assert completed.returncode == 0 + assert "bootstrap_install_performed=1" in completed.stdout + assert install_args.startswith("add --no-cache ") + assert "docker-cli-buildx" in install_args diff --git a/scripts/security/runtime_image_mirror_controller.py b/scripts/security/runtime_image_mirror_controller.py index 4b441df17..351008c7d 100644 --- a/scripts/security/runtime_image_mirror_controller.py +++ b/scripts/security/runtime_image_mirror_controller.py @@ -19,9 +19,9 @@ from typing import Any, Sequence SCHEMA_VERSION = "awoooi_runtime_image_mirror_policy_v1" -RECEIPT_SCHEMA_VERSION = "awoooi_runtime_image_mirror_receipt_v1" +RECEIPT_SCHEMA_VERSION = "awoooi_runtime_image_mirror_receipt_v2" STAGING_SCHEMA_VERSION = "awoooi_runtime_image_mirror_staging_policy_v1" -STAGING_RECEIPT_SCHEMA_VERSION = "awoooi_runtime_image_mirror_staging_receipt_v1" +STAGING_RECEIPT_SCHEMA_VERSION = "awoooi_runtime_image_mirror_staging_receipt_v2" ALLOWED_KINDS = {"deployment", "daemonset", "statefulset"} ALLOWED_CONTAINER_KINDS = {"container", "init_container"} DIGEST_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$") @@ -665,16 +665,25 @@ def mirror_images( known_hosts: Path, apply: bool, ) -> dict[str, Any]: - cache = RuntimeCache(policy, ssh_key, known_hosts) + cache: RuntimeCache | None = None image_receipts: list[dict[str, Any]] = [] missing_count = 0 for image in policy.images: - source_ref = cache.verify_source(image) - source_fingerprint = "sha256:" + hashlib.sha256(source_ref.encode()).hexdigest() target_present = _target_digest_present(image) + source_fingerprint = None + source_cache_verified = False + source_cache_verification_skipped_reason = "target_digest_already_verified" execution = "already_present" if not target_present: missing_count += 1 + if cache is None: + cache = RuntimeCache(policy, ssh_key, known_hosts) + source_ref = cache.verify_source(image) + source_fingerprint = ( + "sha256:" + hashlib.sha256(source_ref.encode()).hexdigest() + ) + source_cache_verified = True + source_cache_verification_skipped_reason = None execution = "check_only_missing" if apply: source_preexisting = _local_image_present(source_ref) @@ -727,7 +736,10 @@ def mirror_images( "platform_digest": image.platform_digest, "target_registry_digest": image.target_registry_digest, "target_digest_ref": _target_digest_ref(image), - "source_cache_verified": True, + "source_cache_verified": source_cache_verified, + "source_cache_verification_skipped_reason": ( + source_cache_verification_skipped_reason + ), "target_digest_verified": target_present, "execution": execution, } @@ -743,6 +755,7 @@ def mirror_images( "mode": "apply" if apply else "check", "external_pull_allowed": False, "source_cache_only": True, + "source_cache_accessed": cache is not None, "candidate_count": len(image_receipts), "missing_before_count": missing_count, "verified_count": sum( @@ -766,20 +779,30 @@ def stage_images( known_hosts: Path, apply: bool, ) -> dict[str, Any]: - cache = RuntimeCache(policy, ssh_key, known_hosts) + cache: RuntimeCache | None = None image_receipts: list[dict[str, Any]] = [] missing_count = 0 for image in policy.images: - source_ref = cache.verify_source(image) - source_fingerprint = "sha256:" + hashlib.sha256(source_ref.encode()).hexdigest() - source_config_digest = cache.source_config_digest(image.platform_digest) - if source_config_digest != image.source_config_digest: - raise ControllerError("runtime_cache_config_digest_mismatch") - descriptor = _registry_descriptor(image.push_ref) + source_fingerprint = None + source_cache_verified = False + source_cache_verification_skipped_reason = ( + "internal_registry_provenance_already_verified" + ) execution = "already_present" if descriptor is None: missing_count += 1 + if cache is None: + cache = RuntimeCache(policy, ssh_key, known_hosts) + source_ref = cache.verify_source(image) + source_fingerprint = ( + "sha256:" + hashlib.sha256(source_ref.encode()).hexdigest() + ) + source_config_digest = cache.source_config_digest(image.platform_digest) + if source_config_digest != image.source_config_digest: + raise ControllerError("runtime_cache_config_digest_mismatch") + source_cache_verified = True + source_cache_verification_skipped_reason = None execution = "check_only_missing" if apply: source_preexisting = _local_image_present(source_ref) @@ -836,7 +859,10 @@ def stage_images( raise ControllerError("internal_registry_descriptor_missing") execution = "staged_from_runtime_cache" - if descriptor is not None and descriptor.config_digest != source_config_digest: + if ( + descriptor is not None + and descriptor.config_digest != image.source_config_digest + ): raise ControllerError("internal_registry_provenance_mismatch") target_digest_ref = None runtime_ref = None @@ -858,13 +884,16 @@ def stage_images( "source_ref_fingerprint": source_fingerprint, "source_index_digest": image.source_index_digest, "platform_digest": image.platform_digest, - "source_config_digest": source_config_digest, + "source_config_digest": image.source_config_digest, "target_registry_digest": ( descriptor.digest if descriptor is not None else None ), "target_digest_ref": target_digest_ref, "runtime_ref": runtime_ref, - "source_cache_verified": True, + "source_cache_verified": source_cache_verified, + "source_cache_verification_skipped_reason": ( + source_cache_verification_skipped_reason + ), "target_digest_verified": target_verified, "provenance_config_digest_match": descriptor is not None, "execution": execution, @@ -889,6 +918,7 @@ def stage_images( "risk_level": policy.risk_level, "external_pull_allowed": False, "source_cache_only": True, + "source_cache_accessed": cache is not None, "candidate_count": len(image_receipts), "missing_before_count": missing_count, "verified_count": sum( diff --git a/scripts/security/tests/test_runtime_image_mirror_controller.py b/scripts/security/tests/test_runtime_image_mirror_controller.py index e5ec0145c..15c3bf820 100644 --- a/scripts/security/tests/test_runtime_image_mirror_controller.py +++ b/scripts/security/tests/test_runtime_image_mirror_controller.py @@ -46,6 +46,10 @@ class RuntimeImageMirrorControllerTest(unittest.TestCase): policy = controller.load_staging_policy(STAGING_POLICY_PATH) return replace(policy, images=(policy.images[0],)) + def _single_image_policy(self): + policy = controller.load_policy(POLICY_PATH) + return replace(policy, images=(policy.images[0],)) + def _image_with_availability_guard(self): image = controller.load_policy(POLICY_PATH).images[0] return replace( @@ -904,12 +908,12 @@ class RuntimeImageMirrorControllerTest(unittest.TestCase): controller.RuntimeCache, "verify_source", return_value="quay.io/argoproj/argocd:v3.3.6", - ), + ) as verify_source, patch.object( controller.RuntimeCache, "source_config_digest", return_value=image.source_config_digest, - ), + ) as source_config_digest, patch.object(controller.RuntimeCache, "export") as export, patch.object( controller, "_registry_descriptor", return_value=descriptor @@ -928,11 +932,85 @@ class RuntimeImageMirrorControllerTest(unittest.TestCase): self.assertEqual(receipt["state"], "verified") self.assertEqual(receipt["missing_before_count"], 0) + self.assertFalse(receipt["source_cache_accessed"]) self.assertEqual(receipt["images"][0]["execution"], "already_present") self.assertTrue(receipt["images"][0]["provenance_config_digest_match"]) + self.assertFalse(receipt["images"][0]["source_cache_verified"]) + self.assertEqual( + receipt["images"][0]["source_cache_verification_skipped_reason"], + "internal_registry_provenance_already_verified", + ) + verify_source.assert_not_called() + source_config_digest.assert_not_called() export.assert_not_called() run.assert_not_called() + def test_mirror_reuses_verified_target_digest_without_runtime_cache(self) -> None: + policy = self._single_image_policy() + with tempfile.TemporaryDirectory() as temp: + receipt_path = Path(temp) / "receipt.json" + with ( + patch.object( + controller.RuntimeCache, + "verify_source", + return_value="quay.io/example/image:v1", + ) as verify_source, + patch.object(controller.RuntimeCache, "export") as export, + patch.object(controller, "_target_digest_present", return_value=True), + patch.object(controller, "_run") as run, + ): + receipt = controller.mirror_images( + policy, + trace_id="aia-p0-006-02a-existing", + receipt_path=receipt_path, + ssh_key=Path("/unused/key"), + known_hosts=Path("/unused/known-hosts"), + apply=True, + ) + + self.assertEqual(receipt["state"], "verified") + self.assertEqual(receipt["missing_before_count"], 0) + self.assertFalse(receipt["source_cache_accessed"]) + self.assertEqual(receipt["images"][0]["execution"], "already_present") + self.assertFalse(receipt["images"][0]["source_cache_verified"]) + self.assertEqual( + receipt["images"][0]["source_cache_verification_skipped_reason"], + "target_digest_already_verified", + ) + verify_source.assert_not_called() + export.assert_not_called() + run.assert_not_called() + + def test_mirror_missing_target_still_verifies_runtime_cache(self) -> None: + policy = self._single_image_policy() + with tempfile.TemporaryDirectory() as temp: + receipt_path = Path(temp) / "receipt.json" + with ( + patch.object( + controller.RuntimeCache, + "verify_source", + return_value="quay.io/example/image:v1", + ) as verify_source, + patch.object(controller.RuntimeCache, "export") as export, + patch.object(controller, "_target_digest_present", return_value=False), + ): + receipt = controller.mirror_images( + policy, + trace_id="aia-p0-006-02a-missing-check", + receipt_path=receipt_path, + ssh_key=Path("/unused/key"), + known_hosts=Path("/unused/known-hosts"), + apply=False, + ) + + self.assertEqual(receipt["state"], "blocked_with_safe_next_action") + self.assertEqual(receipt["missing_before_count"], 1) + self.assertTrue(receipt["source_cache_accessed"]) + self.assertEqual(receipt["images"][0]["execution"], "check_only_missing") + self.assertTrue(receipt["images"][0]["source_cache_verified"]) + verify_source.assert_called_once_with(policy.images[0]) + export.assert_not_called() + def test_staging_exports_cached_source_and_verifies_remote_provenance( self, ) -> None: @@ -985,6 +1063,7 @@ class RuntimeImageMirrorControllerTest(unittest.TestCase): self.assertEqual(receipt["state"], "verified") self.assertEqual(receipt["missing_before_count"], 1) + self.assertTrue(receipt["source_cache_accessed"]) self.assertEqual(receipt["images"][0]["execution"], "staged_from_runtime_cache") self.assertEqual( receipt["images"][0]["target_registry_digest"], descriptor.digest