Compare commits

...

1 Commits

Author SHA1 Message Date
Your Name
5c43b3da2e perf(cd): skip redundant mirror and bootstrap work 2026-07-18 19:39:43 +08:00
5 changed files with 269 additions and 30 deletions

View File

@@ -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:

View File

@@ -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"

View File

@@ -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

View File

@@ -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(

View File

@@ -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