build: pin and verify image inputs
refresh-image-locks / refresh (push) Successful in 27s
lint / lint (push) Successful in 1m3s

This commit is contained in:
2026-07-26 09:24:44 +00:00
parent c094b1ee68
commit a38bbfbae2
17 changed files with 172 additions and 38 deletions
+23 -1
View File
@@ -119,7 +119,13 @@ def docker_cp(src: str, dest: str) -> None:
f"{(result.stderr or '').strip() or '<no stderr>'}")
def build_image(ref: str, context: str, *, dockerfile: str = "") -> None:
def build_image(
ref: str,
context: str,
*,
dockerfile: str = "",
build_args: dict[str, str] | None = None,
) -> None:
"""Invokes `docker build` every call. Layer cache makes no-change
rebuilds cheap; running every time means Dockerfile edits land
without manual `docker rmi`.
@@ -139,10 +145,26 @@ def build_image(ref: str, context: str, *, dockerfile: str = "") -> None:
args.append("--no-cache")
if dockerfile:
args.extend(["-f", dockerfile])
for name, value in (build_args or {}).items():
args.extend(["--build-arg", f"{name}={value}"])
args.append(context)
subprocess.run(args, check=True)
def image_id(ref: str) -> str:
"""Return the exact content-addressed ID for a local image.
This is used when one locally built image is another Dockerfile's base:
passing the ID prevents a mutable tag from being resolved between builds.
"""
result = run_docker(["docker", "image", "inspect", "--format", "{{.Id}}", ref])
image = result.stdout.strip()
if result.returncode != 0 or not image.startswith("sha256:"):
detail = (result.stderr or result.stdout or "").strip()
die(f"could not resolve exact image ID for {ref!r}: {detail or '<no detail>'}")
return image
def verify_agent_image(image: str, argv: tuple[str, ...]) -> None:
"""Run `argv` inside a throwaway container of a freshly built agent
image and die loudly if it fails, instead of shipping an image
@@ -49,9 +49,9 @@ _ARTIFACT_FORMAT = "1"
# from its own generic package; the Dockerfiles baked into each differ (only the
# orchestrator rootfs carries buildah), so the versions are hashed separately.
ROLES = ("orchestrator", "gateway")
_DOCKERFILES = {
_BUILD_INPUTS = {
"orchestrator": ("Dockerfile.orchestrator", "Dockerfile.orchestrator.fc"),
"gateway": ("Dockerfile.gateway",),
"gateway": ("Dockerfile.gateway", "requirements.gateway.lock"),
}
_DEFAULT_BASE = "https://gitea.dideric.is"
@@ -101,7 +101,7 @@ def infra_artifact_version(
h.update(str(path.relative_to(repo_root)).encode())
h.update(b"\0")
h.update(path.read_bytes())
for name in _DOCKERFILES[role]:
for name in _BUILD_INPUTS[role]:
h.update(name.encode())
h.update(b"\0")
h.update((repo_root / name).read_bytes())
+6 -1
View File
@@ -133,10 +133,15 @@ def build_infra_images_with_docker() -> None:
root = str(resources.build_root())
docker_mod.build_image(
_ORCHESTRATOR_IMAGE, root, dockerfile="Dockerfile.orchestrator")
orchestrator_id = docker_mod.image_id(_ORCHESTRATOR_IMAGE)
docker_mod.build_image(
_GATEWAY_IMAGE, root, dockerfile="Dockerfile.gateway")
docker_mod.build_image(
_ORCHESTRATOR_FC_IMAGE, root, dockerfile="Dockerfile.orchestrator.fc")
_ORCHESTRATOR_FC_IMAGE,
root,
dockerfile="Dockerfile.orchestrator.fc",
build_args={"ORCHESTRATOR_BASE_IMAGE": orchestrator_id},
)
def build_rootfs_dir(role: str) -> Path: