feat(infra): pull artifacts pinned by package release

This commit is contained in:
2026-07-27 15:13:52 +00:00
parent 63595f123a
commit 1a9056c648
29 changed files with 621 additions and 56 deletions
+15 -2
View File
@@ -46,8 +46,8 @@ class TestDockerInfraService(unittest.TestCase):
url = self.svc.ensure_running()
self.assertEqual("http://127.0.0.1:8099", url)
# Both images built; the control plane comes up before the gateway.
self.orch.ensure_built.assert_called_once()
self.gw.ensure_built.assert_called_once()
self.orch.ensure_available.assert_called_once()
self.gw.ensure_available.assert_called_once()
self.orch.ensure_running.assert_called_once()
def test_ensure_running_connects_gateway_with_minted_token(self) -> None:
@@ -76,6 +76,19 @@ class TestDockerInfraService(unittest.TestCase):
self.assertEqual("registry-volume", svc.orchestrator()._root_mount_source)
self.assertEqual("ca-volume", svc.gateway()._ca_mount_source)
def test_packaged_images_disable_local_infra_dockerfiles(self) -> None:
refs = [
("registry/orchestrator@sha256:" + "a" * 64, False),
("registry/gateway@sha256:" + "b" * 64, False),
]
with patch(
"bot_bottle.backend.docker.infra.release_manifest.oci_image",
side_effect=refs,
):
svc = DockerInfraService()
self.assertIsNone(svc.orchestrator()._dockerfile)
self.assertIsNone(svc.gateway()._dockerfile)
def test_host_path_and_named_root_mount_are_mutually_exclusive(self) -> None:
with self.assertRaisesRegex(ValueError, "host_root or root_mount_source"):
DockerInfraService(
+5 -4
View File
@@ -231,11 +231,12 @@ class TestDockerOrchestrator(unittest.TestCase):
with self.assertRaises(GatewayError):
self.orch.ensure_built()
def test_ensure_built_noop_without_dockerfile(self) -> None:
orch = DockerOrchestrator(dockerfile=None)
with patch(_RUN) as run:
def test_ensure_built_pulls_without_dockerfile(self) -> None:
orch = DockerOrchestrator("registry/orchestrator@sha256:" + "a" * 64,
dockerfile=None)
with patch(_RUN, return_value=_proc()) as run:
orch.ensure_built()
run.assert_not_called()
run.assert_called_once_with(["docker", "pull", orch.image_ref])
def test_is_running_reads_docker_ps(self) -> None:
with patch(_RUN, return_value=_proc(stdout=ORCHESTRATOR_NAME)):
+4 -4
View File
@@ -53,7 +53,7 @@ class TestEnsureRunning(unittest.TestCase):
patch.object(infra_vm, "_health_ok", return_value=True), \
patch.object(infra_vm, "_pidfile_alive", return_value=True), \
patch.object(infra_vm, "stop") as stop, \
patch.object(infra_vm, "ensure_built") as built:
patch.object(infra_vm, "ensure_available") as built:
url = FirecrackerInfraService().ensure_running()
stop.assert_not_called()
built.assert_not_called()
@@ -72,7 +72,7 @@ class TestEnsureRunning(unittest.TestCase):
patch.object(infra_vm, "_health_ok", return_value=True), \
patch.object(infra_vm, "_pidfile_alive", return_value=True), \
patch.object(infra_vm, "stop") as stop, \
patch.object(infra_vm, "ensure_built"), \
patch.object(infra_vm, "ensure_available"), \
patch(_ORCH_CLS) as orch_cls, patch(_GW_CLS) as gw_cls:
orch_cls.return_value.gateway_url.return_value = "http://10.243.255.1:8099"
orch_cls.return_value.mint_gateway_token.return_value = "gw.jwt"
@@ -98,7 +98,7 @@ class TestEnsureRunning(unittest.TestCase):
patch.object(infra_vm, "_health_ok", return_value=True), \
patch.object(infra_vm, "_pidfile_alive", return_value=False), \
patch.object(infra_vm, "stop") as stop, \
patch.object(infra_vm, "ensure_built"), \
patch.object(infra_vm, "ensure_available"), \
patch(_ORCH_CLS) as orch_cls, patch(_GW_CLS) as gw_cls:
FirecrackerInfraService().ensure_running()
stop.assert_called_once()
@@ -111,7 +111,7 @@ class TestEnsureRunning(unittest.TestCase):
patch.object(infra_vm, "expected_version", return_value="v-current"), \
patch.object(infra_vm, "_health_ok", return_value=False), \
patch.object(infra_vm, "stop") as stop, \
patch.object(infra_vm, "ensure_built") as built, \
patch.object(infra_vm, "ensure_available") as built, \
patch(_ORCH_CLS) as orch_cls, patch(_GW_CLS) as gw_cls:
FirecrackerInfraService().ensure_running()
stop.assert_called_once() # clear stale VMs first
@@ -0,0 +1,28 @@
from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from scripts.generate_release_manifest import main
class TestGenerateReleaseManifest(unittest.TestCase):
def test_writes_validated_manifest(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
output = Path(tmp) / "release.json"
rc = main([
"--source-commit", "1" * 40,
"--orchestrator-image",
"registry/orchestrator@sha256:" + "a" * 64,
"--gateway-image", "registry/gateway@sha256:" + "b" * 64,
"--firecracker-orchestrator-version", "orch-v1",
"--firecracker-orchestrator-sha256", "c" * 64,
"--firecracker-gateway-version", "gateway-v1",
"--firecracker-gateway-sha256", "d" * 64,
"--output", str(output),
])
self.assertEqual(0, rc)
data = json.loads(output.read_text(encoding="utf-8"))
self.assertEqual("orch-v1", data["firecracker"]["orchestrator"]["version"])
+14 -2
View File
@@ -33,8 +33,8 @@ class TestInfraEnsureRunning(unittest.TestCase):
with patch(f"{_INFRA}.ensure_networks") as nets:
url = self.svc.ensure_running()
nets.assert_called_once()
self.orch.ensure_built.assert_called_once()
self.gw.ensure_built.assert_called_once()
self.orch.ensure_available.assert_called_once()
self.gw.ensure_available.assert_called_once()
self.orch.ensure_running.assert_called_once()
# Returns the host control-plane URL (the InfraService contract).
self.assertEqual("http://192.168.128.2:8099", url)
@@ -51,6 +51,18 @@ class TestInfraEnsureRunning(unittest.TestCase):
self.assertTrue(self.svc.is_healthy())
self.orch.is_healthy.assert_called_once()
def test_packaged_images_disable_local_infra_builds(self) -> None:
refs = [
("registry/orchestrator@sha256:" + "a" * 64, False),
("registry/gateway@sha256:" + "b" * 64, False),
]
with patch(
f"{_INFRA}.release_manifest.oci_image", side_effect=refs,
):
svc = MacosInfraService(repo_root=Path("/r"))
self.assertFalse(svc.orchestrator()._local_build)
self.assertFalse(svc.gateway()._local_build)
class TestCaCertPem(unittest.TestCase):
def test_delegates_to_the_gateway_service(self) -> None:
+17 -2
View File
@@ -30,14 +30,16 @@ def _spec(src: str, tgt: str, readonly: bool = False) -> str:
class TestMacosOrchestratorRun(unittest.TestCase):
def _run(self) -> list[str]:
def _run(self, *, local_build: bool = True) -> list[str]:
run = Mock(return_value=_ok())
with patch(f"{_ORCH}.container_mod") as mod, \
patch("bot_bottle.trust_domain.host_signing_key", return_value="k"):
mod.dns_server.return_value = "1.1.1.1"
mod.bind_mount_spec.side_effect = _spec
mod.run_container_argv = run
MacosOrchestrator(repo_root=Path("/r"))._run_container("h1")
MacosOrchestrator(
repo_root=Path("/r"), local_build=local_build,
)._run_container("h1")
return run.call_args.args[0]
def test_runs_on_the_control_network_only(self) -> None:
@@ -63,6 +65,11 @@ class TestMacosOrchestratorRun(unittest.TestCase):
def test_source_hash_is_labelled_for_recreate(self) -> None:
self.assertIn("BOT_BOTTLE_SOURCE_HASH=h1", self._run())
def test_packaged_image_does_not_overlay_host_source(self) -> None:
argv = self._run(local_build=False)
self.assertNotIn("--mount", argv)
self.assertFalse(any(arg.startswith("PYTHONPATH=") for arg in argv))
def test_start_failure_raises(self) -> None:
with patch(f"{_ORCH}.container_mod") as mod, \
patch("bot_bottle.trust_domain.host_signing_key", return_value="k"):
@@ -145,6 +152,14 @@ class TestMacosOrchestratorBuildStop(unittest.TestCase):
_args, kwargs = mod.build_image.call_args
self.assertEqual("Dockerfile.orchestrator", kwargs["dockerfile"])
def test_ensure_built_pulls_packaged_image(self) -> None:
image = "registry/orchestrator@sha256:" + "a" * 64
orch = MacosOrchestrator(image, local_build=False)
with patch(f"{_ORCH}.container_mod") as mod:
orch.ensure_built()
mod.pull_image.assert_called_once_with(image)
mod.build_image.assert_not_called()
def test_stop_removes_the_container(self) -> None:
orch = MacosOrchestrator()
with patch(f"{_ORCH}.container_mod") as mod:
+4 -4
View File
@@ -427,11 +427,11 @@ class TestDockerGatewayBuild(unittest.TestCase):
builds = [c for c in calls if c[:2] == ["docker", "build"]]
self.assertIn("--no-cache", builds[0])
def test_ensure_built_noop_when_no_dockerfile(self) -> None:
sc = DockerGateway("busybox", dockerfile=None)
with patch(_RUN_DOCKER) as m:
def test_ensure_built_pulls_when_no_dockerfile(self) -> None:
sc = DockerGateway("registry/gateway@sha256:" + "a" * 64, dockerfile=None)
with patch(_RUN_DOCKER, return_value=_proc()) as m:
sc.ensure_built()
m.assert_not_called()
m.assert_called_once_with(["docker", "pull", sc.image_ref])
def test_ensure_built_raises_on_build_failure(self) -> None:
with patch(_RUN_DOCKER, return_value=_proc(returncode=1, stderr="build boom")):
+86
View File
@@ -0,0 +1,86 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from bot_bottle.release_manifest import (
ReleaseManifestError,
oci_image,
packaged_manifest,
parse_manifest,
)
def manifest() -> dict[str, object]:
return {
"schema": 1,
"source_commit": "1" * 40,
"oci": {
"orchestrator": "registry/orchestrator@sha256:" + "a" * 64,
"gateway": "registry/gateway@sha256:" + "b" * 64,
},
"firecracker": {
"orchestrator": {"version": "orch-v1", "sha256": "c" * 64},
"gateway": {"version": "gateway-v1", "sha256": "d" * 64},
},
}
class TestParseManifest(unittest.TestCase):
def test_parses_all_backend_artifacts(self) -> None:
parsed = parse_manifest(manifest())
self.assertTrue(parsed.orchestrator_image.endswith("a" * 64))
self.assertEqual("orch-v1", parsed.firecracker_orchestrator.version)
def test_rejects_mutable_oci_reference(self) -> None:
data = manifest()
assert isinstance(data["oci"], dict)
data["oci"]["orchestrator"] = "registry/orchestrator:latest"
with self.assertRaisesRegex(ReleaseManifestError, "digest-pinned"):
parse_manifest(data)
def test_rejects_malformed_firecracker_checksum(self) -> None:
data = manifest()
assert isinstance(data["firecracker"], dict)
artifact = data["firecracker"]["gateway"]
assert isinstance(artifact, dict)
artifact["sha256"] = "nope"
with self.assertRaisesRegex(ReleaseManifestError, "64 lowercase hex"):
parse_manifest(data)
class TestImageSelection(unittest.TestCase):
def test_development_marker_selects_local_builds(self) -> None:
with patch(
"bot_bottle.release_manifest.resources.is_source_checkout",
return_value=False,
), patch(
"bot_bottle.release_manifest.manifest_path",
) as path:
path.return_value.read_text.return_value = (
'{"schema": 1, "development": true}')
self.assertIsNone(packaged_manifest())
def test_packaged_install_uses_manifest_reference_without_build(self) -> None:
parsed = parse_manifest(manifest())
with patch(
"bot_bottle.release_manifest.packaged_manifest", return_value=parsed,
), patch(
"bot_bottle.release_manifest.local_build_requested", return_value=False,
), patch.dict("os.environ", {}, clear=True):
ref, local = oci_image("orchestrator", "local:latest")
self.assertEqual(parsed.orchestrator_image, ref)
self.assertFalse(local)
def test_mutable_override_fails_outside_local_mode(self) -> None:
parsed = parse_manifest(manifest())
with patch(
"bot_bottle.release_manifest.packaged_manifest", return_value=parsed,
), patch(
"bot_bottle.release_manifest.local_build_requested", return_value=False,
), patch.dict(
"os.environ", {"BOT_BOTTLE_ORCHESTRATOR_IMAGE": "local:latest"},
clear=True,
):
with self.assertRaisesRegex(ReleaseManifestError, "digest-pinned"):
oci_image("orchestrator", "fallback:latest")
+9
View File
@@ -117,6 +117,15 @@ class TestWheelInstall(unittest.TestCase):
proc = self._run(["-c", script])
self.assertIn("SELF_CONTAINED_OK", proc.stdout, msg=proc.stderr)
def test_ad_hoc_wheel_is_marked_for_local_infrastructure_builds(self):
script = (
"from bot_bottle.release_manifest import packaged_manifest\n"
"assert packaged_manifest() is None\n"
"print('DEVELOPMENT_MANIFEST_OK')\n"
)
proc = self._run(["-c", script])
self.assertIn("DEVELOPMENT_MANIFEST_OK", proc.stdout, msg=proc.stderr)
if __name__ == "__main__":
unittest.main()