refactor(backend): rename consolidated_launch -> infra_launch (0081 review)

Mechanical rename addressing review 6141 on #519: "consolidated launch" was
unclear about what it composes. Rename the per-backend module
`consolidated_launch.py` -> `infra_launch.py` and the error
`ConsolidatedLaunchError` -> `InfraLaunchError` across all three backends and
their callers/tests. Unify the three identical per-backend error classes into
one `InfraLaunchError` defined in `backend/base.py` (re-exported by each
`infra_launch`) so the base backend can raise and catch a single shared type —
groundwork for the base owning the gateway-attach flow.

Add a glossary entry defining **infra** = the per-host gateway + orchestrator
service pair every bottle attaches to.

No behavior change.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 01:14:14 +00:00
parent de80aafb1f
commit 4f2ccaed29
19 changed files with 61 additions and 54 deletions
+6
View File
@@ -264,6 +264,12 @@ PlanT = TypeVar("PlanT", bound=BottlePlan)
CleanupT = TypeVar("CleanupT", bound=BottleCleanupPlan) CleanupT = TypeVar("CleanupT", bound=BottleCleanupPlan)
class InfraLaunchError(RuntimeError):
"""A per-host infra (gateway + orchestrator) launch/reconcile step could not
complete. Shared across backends so the base backend can raise + catch one
type; each backend's ``infra_launch`` module re-exports it."""
@dataclass(frozen=True) @dataclass(frozen=True)
class BottleImages: class BottleImages:
"""Resolved image references (or artifact paths) for a bottle launch. """Resolved image references (or artifact paths) for a bottle launch.
+1 -1
View File
@@ -142,5 +142,5 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
return _enumerate.enumerate_active() return _enumerate.enumerate_active()
def attach_bottled_agents_to_gateway(self) -> None: def attach_bottled_agents_to_gateway(self) -> None:
from .consolidated_launch import attach_bottled_agents_to_gateway from .infra_launch import attach_bottled_agents_to_gateway
attach_bottled_agents_to_gateway() attach_bottled_agents_to_gateway()
@@ -25,16 +25,13 @@ from ...gateway import GATEWAY_NETWORK
from .infra import INFRA_NAME, DockerInfraService from .infra import INFRA_NAME, DockerInfraService
from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME
from ...orchestrator.reprovision import reprovision_bottles from ...orchestrator.reprovision import reprovision_bottles
from ..base import InfraLaunchError
from ..provision_bottle import deprovision_bottle, provision_bottle from ..provision_bottle import deprovision_bottle, provision_bottle
from ..util import AGENT_CA_PATH from ..util import AGENT_CA_PATH
from .gateway_transport import DockerGatewayTransport from .gateway_transport import DockerGatewayTransport
from .gateway_net import next_free_ip from .gateway_net import next_free_ip
class ConsolidatedLaunchError(RuntimeError):
"""The consolidated register/provision sequence could not complete."""
@dataclass(frozen=True) @dataclass(frozen=True)
class LaunchContext: class LaunchContext:
"""What the agent container needs to join the shared gateway.""" """What the agent container needs to join the shared gateway."""
@@ -56,7 +53,7 @@ def _network_cidr(network: str) -> str:
]) ])
cidr = proc.stdout.strip() cidr = proc.stdout.strip()
if proc.returncode != 0 or not cidr: if proc.returncode != 0 or not cidr:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"gateway network {network} has no subnet: {proc.stderr.strip()}" f"gateway network {network} has no subnet: {proc.stderr.strip()}"
) )
return cidr return cidr
@@ -80,13 +77,13 @@ def _push_ca_to_container(container: str, ca_pem: str) -> None:
"""Replace one running agent container's trusted gateway CA with `ca_pem` """Replace one running agent container's trusted gateway CA with `ca_pem`
and rebuild its trust store via `docker cp` + `docker exec`. Unconditional and rebuild its trust store via `docker cp` + `docker exec`. Unconditional
install there is one gateway, so no fingerprint match is needed. Raises install there is one gateway, so no fingerprint match is needed. Raises
`ConsolidatedLaunchError` on failure.""" `InfraLaunchError` on failure."""
mkdir = run_docker( mkdir = run_docker(
["docker", "exec", container, "mkdir", "-p", ["docker", "exec", container, "mkdir", "-p",
"/usr/local/share/ca-certificates"] "/usr/local/share/ca-certificates"]
) )
if mkdir.returncode != 0: if mkdir.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {container} failed (mkdir): {mkdir.stderr.strip()}" f"CA push to {container} failed (mkdir): {mkdir.stderr.strip()}"
) )
with tempfile.NamedTemporaryFile("w", suffix=".crt") as tmp: with tempfile.NamedTemporaryFile("w", suffix=".crt") as tmp:
@@ -94,7 +91,7 @@ def _push_ca_to_container(container: str, ca_pem: str) -> None:
tmp.flush() tmp.flush()
cp = run_docker(["docker", "cp", tmp.name, f"{container}:{AGENT_CA_PATH}"]) cp = run_docker(["docker", "cp", tmp.name, f"{container}:{AGENT_CA_PATH}"])
if cp.returncode != 0: if cp.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {container} failed (cp): {cp.stderr.strip()}" f"CA push to {container} failed (cp): {cp.stderr.strip()}"
) )
ex = run_docker( ex = run_docker(
@@ -102,7 +99,7 @@ def _push_ca_to_container(container: str, ca_pem: str) -> None:
f"chmod 644 {AGENT_CA_PATH} && update-ca-certificates"] f"chmod 644 {AGENT_CA_PATH} && update-ca-certificates"]
) )
if ex.returncode != 0: if ex.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {container} failed (update-ca-certificates): " f"CA push to {container} failed (update-ca-certificates): "
f"{ex.stderr.strip()}" f"{ex.stderr.strip()}"
) )
@@ -138,7 +135,7 @@ def attach_bottled_agents_to_gateway(
try: try:
_push_ca_to_container(name, ca_pem) _push_ca_to_container(name, ca_pem)
reconciled += 1 reconciled += 1
except (OSError, ConsolidatedLaunchError) as exc: except (OSError, InfraLaunchError) as exc:
log.info(f"CA reconcile skipped for {name}: {exc}") log.info(f"CA reconcile skipped for {name}: {exc}")
if reconciled: if reconciled:
log.info( log.info(
@@ -255,5 +252,5 @@ __all__ = [
"launch_consolidated", "launch_consolidated",
"attach_bottled_agents_to_gateway", "attach_bottled_agents_to_gateway",
"deprovision_consolidated", "deprovision_consolidated",
"ConsolidatedLaunchError", "InfraLaunchError",
] ]
+1 -1
View File
@@ -61,7 +61,7 @@ from .compose import (
) )
from .consolidated_compose import consolidated_agent_compose from .consolidated_compose import consolidated_agent_compose
from ...orchestrator.store.config_store import resolve_teardown_timeout from ...orchestrator.store.config_store import resolve_teardown_timeout
from .consolidated_launch import launch_consolidated, deprovision_consolidated from .infra_launch import launch_consolidated, deprovision_consolidated
from .infra import INFRA_NAME from .infra import INFRA_NAME
from .gateway import DockerGateway from .gateway import DockerGateway
from ... import resources from ... import resources
+1 -1
View File
@@ -120,7 +120,7 @@ class FirecrackerBottleBackend(
return _enumerate.enumerate_active() return _enumerate.enumerate_active()
def attach_bottled_agents_to_gateway(self) -> None: def attach_bottled_agents_to_gateway(self) -> None:
from .consolidated_launch import attach_bottled_agents_to_gateway from .infra_launch import attach_bottled_agents_to_gateway
attach_bottled_agents_to_gateway() attach_bottled_agents_to_gateway()
def supervise_mcp_url(self, plan: FirecrackerBottlePlan) -> str: def supervise_mcp_url(self, plan: FirecrackerBottlePlan) -> str:
@@ -41,6 +41,7 @@ from ...orchestrator.lifecycle import (
) )
from ...orchestrator.reprovision import reprovision_bottles from ...orchestrator.reprovision import reprovision_bottles
from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME
from ..base import InfraLaunchError
from ..provision_bottle import deprovision_bottle, provision_bottle from ..provision_bottle import deprovision_bottle, provision_bottle
from ..util import AGENT_CA_PATH from ..util import AGENT_CA_PATH
from . import cleanup, util from . import cleanup, util
@@ -50,10 +51,6 @@ from .infra import FirecrackerInfraService
_ENV_VAR_SECRET_PATH = "/run/bot-bottle/env-var-secret" _ENV_VAR_SECRET_PATH = "/run/bot-bottle/env-var-secret"
class ConsolidatedLaunchError(RuntimeError):
"""The consolidated register/provision sequence could not complete."""
@dataclass(frozen=True) @dataclass(frozen=True)
class LaunchContext: class LaunchContext:
"""What the Firecracker launch needs from the consolidated sequence.""" """What the Firecracker launch needs from the consolidated sequence."""
@@ -85,7 +82,7 @@ def persist_env_var_secret(private_key: Path, guest_ip: str, secret: str) -> Non
input=secret, capture_output=True, text=True, check=False, input=secret, capture_output=True, text=True, check=False,
) )
if proc.returncode != 0: if proc.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"failed to persist {ENV_VAR_SECRET_NAME} in agent VM: " f"failed to persist {ENV_VAR_SECRET_NAME} in agent VM: "
f"{proc.stderr.strip() or '<no stderr>'}" f"{proc.stderr.strip() or '<no stderr>'}"
) )
@@ -95,7 +92,7 @@ def _push_gateway_ca_to_agent(private_key: Path, guest_ip: str, ca_pem: str) ->
"""Replace one running agent VM's trusted gateway CA with `ca_pem` and """Replace one running agent VM's trusted gateway CA with `ca_pem` and
rebuild its trust store over SSH. Unconditional install there is one rebuild its trust store over SSH. Unconditional install there is one
gateway, so no fingerprint match is needed. Bare-pipe input keeps the cert gateway, so no fingerprint match is needed. Bare-pipe input keeps the cert
off argv. Raises `ConsolidatedLaunchError` on failure.""" off argv. Raises `InfraLaunchError` on failure."""
install = ( install = (
"umask 022; mkdir -p /usr/local/share/ca-certificates && " "umask 022; mkdir -p /usr/local/share/ca-certificates && "
f"cat > {AGENT_CA_PATH} && chmod 644 {AGENT_CA_PATH} && " f"cat > {AGENT_CA_PATH} && chmod 644 {AGENT_CA_PATH} && "
@@ -106,7 +103,7 @@ def _push_gateway_ca_to_agent(private_key: Path, guest_ip: str, ca_pem: str) ->
input=ca_pem, capture_output=True, text=True, check=False, input=ca_pem, capture_output=True, text=True, check=False,
) )
if proc.returncode != 0: if proc.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {guest_ip} failed: " f"CA push to {guest_ip} failed: "
f"{proc.stderr.strip() or '<no stderr>'}" f"{proc.stderr.strip() or '<no stderr>'}"
) )
@@ -135,7 +132,7 @@ def attach_bottled_agents_to_gateway() -> None:
try: try:
_push_gateway_ca_to_agent(private_key, guest_ip, ca_pem) _push_gateway_ca_to_agent(private_key, guest_ip, ca_pem)
reconciled += 1 reconciled += 1
except (OSError, ConsolidatedLaunchError) as exc: except (OSError, InfraLaunchError) as exc:
info(f"CA reconcile skipped for {guest_ip}: {exc}") info(f"CA reconcile skipped for {guest_ip}: {exc}")
if reconciled: if reconciled:
info(f"reconciled gateway CA into {reconciled} running Firecracker bottle(s)") info(f"reconciled gateway CA into {reconciled} running Firecracker bottle(s)")
@@ -215,6 +212,6 @@ __all__ = [
"launch_consolidated", "launch_consolidated",
"attach_bottled_agents_to_gateway", "attach_bottled_agents_to_gateway",
"deprovision_consolidated", "deprovision_consolidated",
"ConsolidatedLaunchError", "InfraLaunchError",
"OrchestratorStartError", "OrchestratorStartError",
] ]
+1 -1
View File
@@ -51,7 +51,7 @@ from .bottle import FirecrackerBottle
from .bottle_plan import FirecrackerBottlePlan from .bottle_plan import FirecrackerBottlePlan
from ...orchestrator.store.config_store import resolve_teardown_timeout from ...orchestrator.store.config_store import resolve_teardown_timeout
from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME
from .consolidated_launch import ( from .infra_launch import (
launch_consolidated, launch_consolidated,
persist_env_var_secret, persist_env_var_secret,
deprovision_consolidated, deprovision_consolidated,
@@ -118,7 +118,7 @@ class MacosContainerBottleBackend(
return _enumerate.enumerate_active() return _enumerate.enumerate_active()
def attach_bottled_agents_to_gateway(self) -> None: def attach_bottled_agents_to_gateway(self) -> None:
from .consolidated_launch import attach_bottled_agents_to_gateway from .infra_launch import attach_bottled_agents_to_gateway
attach_bottled_agents_to_gateway() attach_bottled_agents_to_gateway()
def supervise_mcp_url(self, plan: MacosContainerBottlePlan) -> str: def supervise_mcp_url(self, plan: MacosContainerBottlePlan) -> str:
@@ -43,6 +43,7 @@ from ...log import info
from ...orchestrator.client import OrchestratorClient, OrchestratorClientError from ...orchestrator.client import OrchestratorClient, OrchestratorClientError
from ...orchestrator.reprovision import reprovision_bottles from ...orchestrator.reprovision import reprovision_bottles
from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME
from ..base import InfraLaunchError
from ..provision_bottle import deprovision_bottle, provision_bottle from ..provision_bottle import deprovision_bottle, provision_bottle
from ..util import AGENT_CA_PATH from ..util import AGENT_CA_PATH
from . import util as container_mod from . import util as container_mod
@@ -52,10 +53,6 @@ from .gateway_transport import MacosGatewayTransport
from .infra import MacosInfraService, OrchestratorStartError from .infra import MacosInfraService, OrchestratorStartError
class ConsolidatedLaunchError(RuntimeError):
"""The consolidated register/provision sequence could not complete."""
@dataclass(frozen=True) @dataclass(frozen=True)
class GatewayEndpoint: class GatewayEndpoint:
"""What the agent `container run` needs to reach the shared gateway. """What the agent `container run` needs to reach the shared gateway.
@@ -104,13 +101,13 @@ def _push_ca_to_container(name: str, ca_pem: str) -> None:
"""Replace one running agent container's trusted gateway CA with `ca_pem` """Replace one running agent container's trusted gateway CA with `ca_pem`
and rebuild its trust store via `container cp` + `container exec`. and rebuild its trust store via `container cp` + `container exec`.
Unconditional install there is one gateway, so no fingerprint match is Unconditional install there is one gateway, so no fingerprint match is
needed. Raises `ConsolidatedLaunchError` on failure.""" needed. Raises `InfraLaunchError` on failure."""
mkdir = container_mod.run_container_argv( mkdir = container_mod.run_container_argv(
["container", "exec", name, "mkdir", "-p", ["container", "exec", name, "mkdir", "-p",
"/usr/local/share/ca-certificates"] "/usr/local/share/ca-certificates"]
) )
if mkdir.returncode != 0: if mkdir.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {name} failed (mkdir): {(mkdir.stderr or '').strip()}" f"CA push to {name} failed (mkdir): {(mkdir.stderr or '').strip()}"
) )
with tempfile.NamedTemporaryFile("w", suffix=".crt") as tmp: with tempfile.NamedTemporaryFile("w", suffix=".crt") as tmp:
@@ -120,7 +117,7 @@ def _push_ca_to_container(name: str, ca_pem: str) -> None:
["container", "cp", tmp.name, f"{name}:{AGENT_CA_PATH}"] ["container", "cp", tmp.name, f"{name}:{AGENT_CA_PATH}"]
) )
if cp.returncode != 0: if cp.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {name} failed (cp): {(cp.stderr or '').strip()}" f"CA push to {name} failed (cp): {(cp.stderr or '').strip()}"
) )
ex = container_mod.run_container_argv( ex = container_mod.run_container_argv(
@@ -128,7 +125,7 @@ def _push_ca_to_container(name: str, ca_pem: str) -> None:
f"chmod 644 {AGENT_CA_PATH} && update-ca-certificates"] f"chmod 644 {AGENT_CA_PATH} && update-ca-certificates"]
) )
if ex.returncode != 0: if ex.returncode != 0:
raise ConsolidatedLaunchError( raise InfraLaunchError(
f"CA push to {name} failed (update-ca-certificates): " f"CA push to {name} failed (update-ca-certificates): "
f"{(ex.stderr or '').strip()}" f"{(ex.stderr or '').strip()}"
) )
@@ -154,7 +151,7 @@ def attach_bottled_agents_to_gateway(ca_pem: str | None = None) -> None:
try: try:
_push_ca_to_container(name, ca_pem) _push_ca_to_container(name, ca_pem)
reconciled += 1 reconciled += 1
except (OSError, ConsolidatedLaunchError) as exc: except (OSError, InfraLaunchError) as exc:
info(f"CA reconcile skipped for {name}: {exc}") info(f"CA reconcile skipped for {name}: {exc}")
if reconciled: if reconciled:
info(f"reconciled gateway CA into {reconciled} running macOS bottle(s)") info(f"reconciled gateway CA into {reconciled} running macOS bottle(s)")
@@ -266,7 +263,7 @@ __all__ = [
"live_source_ips", "live_source_ips",
"register_agent", "register_agent",
"deprovision_consolidated", "deprovision_consolidated",
"ConsolidatedLaunchError", "InfraLaunchError",
"OrchestratorStartError", "OrchestratorStartError",
"GATEWAY_NETWORK", "GATEWAY_NETWORK",
] ]
+2 -2
View File
@@ -5,7 +5,7 @@ proxies egress through the one per-host gateway, replacing the per-bottle
companion container removed in #385. companion container removed in #385.
The order differs from docker's, forced by Apple Container 1.0.0 having no The order differs from docker's, forced by Apple Container 1.0.0 having no
`--ip` (see `consolidated_launch`): the agent is started *before* it is `--ip` (see `infra_launch`): the agent is started *before* it is
registered, because its DHCP-assigned address the attribution key does not registered, because its DHCP-assigned address the attribution key does not
exist until then. exist until then.
@@ -64,7 +64,7 @@ from . import nested_containers as nested_containers_mod
from .bottle_plan import MacosContainerBottlePlan from .bottle_plan import MacosContainerBottlePlan
from ...orchestrator.store.config_store import resolve_teardown_timeout from ...orchestrator.store.config_store import resolve_teardown_timeout
from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME, new_env_var_secret from ...orchestrator.store.secret_store import ENV_VAR_SECRET_NAME, new_env_var_secret
from .consolidated_launch import ( from .infra_launch import (
GatewayEndpoint, GatewayEndpoint,
ensure_gateway, ensure_gateway,
register_agent, register_agent,
+1 -1
View File
@@ -2,7 +2,7 @@
Register a bottle with the orchestrator and provision its git-gate state into Register a bottle with the orchestrator and provision its git-gate state into
the shared gateway (`provision_bottle`), and the inverse teardown the shared gateway (`provision_bottle`), and the inverse teardown
(`deprovision_bottle`). Backend-neutral each backend's consolidated_launch (`deprovision_bottle`). Backend-neutral each backend's infra_launch
drives these through its own `GatewayTransport` rather than re-implementing drives these through its own `GatewayTransport` rather than re-implementing
them. The git-gate half of the work lives in `provision_gateway`. them. The git-gate half of the work lives in `provision_gateway`.
""" """
+10
View File
@@ -55,3 +55,13 @@ agent has two observable states:
Shorthand for an active (running) Bottled Agent. Used in the supervisor TUI Shorthand for an active (running) Bottled Agent. Used in the supervisor TUI
and discovery layer to mean "a bottle whose agent runtime is currently up." and discovery layer to mean "a bottle whose agent runtime is currently up."
## Infra
The per-host pair of shared services every bottle attaches to: the
**orchestrator** (control plane — holds the signing key, owns the registry DB,
mints tokens) and the **gateway** (data plane — TLS-inspecting egress proxy,
git-gate, supervise). Brought up as an idempotent singleton pair by each
backend's `InfraService` (two containers on docker/macOS, two microVMs on
Firecracker). The per-backend `infra_launch` module composes the bring-up and
bottle register/provision sequence against this pair.
@@ -21,7 +21,7 @@ import subprocess
import time import time
import unittest import unittest
from bot_bottle.backend.docker.consolidated_launch import ( from bot_bottle.backend.docker.infra_launch import (
_network_cidr, _network_cidr,
_network_container_ips, _network_container_ips,
) )
@@ -11,9 +11,9 @@ from types import SimpleNamespace
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from bot_bottle.orchestrator.reprovision import reprovision_bottles from bot_bottle.orchestrator.reprovision import reprovision_bottles
from bot_bottle.backend.firecracker import consolidated_launch as fc from bot_bottle.backend.firecracker import infra_launch as fc
from bot_bottle.backend.macos_container import consolidated_launch as mac from bot_bottle.backend.macos_container import infra_launch as mac
from bot_bottle.backend.docker import consolidated_launch as docker from bot_bottle.backend.docker import infra_launch as docker
from bot_bottle.orchestrator.client import OrchestratorClientError from bot_bottle.orchestrator.client import OrchestratorClientError
@@ -132,7 +132,7 @@ class TestFirecrackerReprovision(unittest.TestCase):
def test_persist_failure_is_fatal_to_launch(self) -> None: def test_persist_failure_is_fatal_to_launch(self) -> None:
with patch.object(fc.util, "ssh_base_argv", return_value=["ssh", "guest"]), \ with patch.object(fc.util, "ssh_base_argv", return_value=["ssh", "guest"]), \
patch.object(fc.subprocess, "run", return_value=_proc(1, stderr="denied")): patch.object(fc.subprocess, "run", return_value=_proc(1, stderr="denied")):
with self.assertRaisesRegex(fc.ConsolidatedLaunchError, "denied"): with self.assertRaisesRegex(fc.InfraLaunchError, "denied"):
fc.persist_env_var_secret(Path("/key"), "10.0.0.1", "secret") fc.persist_env_var_secret(Path("/key"), "10.0.0.1", "secret")
def test_reads_live_vm_key_and_reprovisions(self) -> None: def test_reads_live_vm_key_and_reprovisions(self) -> None:
@@ -16,7 +16,7 @@ from bot_bottle.agent_provider import AgentProvisionPlan
from bot_bottle.backend import BottleSpec from bot_bottle.backend import BottleSpec
from bot_bottle.backend.docker import launch as launch_mod from bot_bottle.backend.docker import launch as launch_mod
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.backend.docker.consolidated_launch import LaunchContext from bot_bottle.backend.docker.infra_launch import LaunchContext
from bot_bottle.egress import EgressPlan from bot_bottle.egress import EgressPlan
from bot_bottle.git_gate import GitGatePlan from bot_bottle.git_gate import GitGatePlan
from bot_bottle.log import Die from bot_bottle.log import Die
+1 -1
View File
@@ -19,7 +19,7 @@ from bot_bottle.agent_provider import AgentProvisionPlan
from bot_bottle.backend import BottleImages, BottleSpec from bot_bottle.backend import BottleImages, BottleSpec
from bot_bottle.backend.docker import launch as launch_mod from bot_bottle.backend.docker import launch as launch_mod
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.backend.docker.consolidated_launch import LaunchContext from bot_bottle.backend.docker.infra_launch import LaunchContext
from bot_bottle.egress import EgressPlan from bot_bottle.egress import EgressPlan
from bot_bottle.git_gate import GitGatePlan from bot_bottle.git_gate import GitGatePlan
from bot_bottle.manifest import ManifestIndex from bot_bottle.manifest import ManifestIndex
@@ -6,7 +6,7 @@ import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, Mock, patch from unittest.mock import MagicMock, Mock, patch
from bot_bottle.backend.docker.consolidated_launch import ( from bot_bottle.backend.docker.infra_launch import (
launch_consolidated, launch_consolidated,
deprovision_consolidated, deprovision_consolidated,
) )
@@ -14,7 +14,7 @@ from bot_bottle.egress import EgressPlan, EgressRoute
from bot_bottle.git_gate import GitGatePlan from bot_bottle.git_gate import GitGatePlan
from bot_bottle.orchestrator.client import RegisteredBottle from bot_bottle.orchestrator.client import RegisteredBottle
_MOD = "bot_bottle.backend.docker.consolidated_launch" _MOD = "bot_bottle.backend.docker.infra_launch"
_UTIL = "bot_bottle.backend.provision_bottle" _UTIL = "bot_bottle.backend.provision_bottle"
@@ -16,7 +16,7 @@ from unittest.mock import ANY, patch
from bot_bottle.backend.macos_container.bottle import MacosContainerBottle from bot_bottle.backend.macos_container.bottle import MacosContainerBottle
from bot_bottle.backend.macos_container.bottle_plan import MacosContainerBottlePlan from bot_bottle.backend.macos_container.bottle_plan import MacosContainerBottlePlan
from bot_bottle.backend.macos_container.consolidated_launch import GatewayEndpoint from bot_bottle.backend.macos_container.infra_launch import GatewayEndpoint
from bot_bottle.backend.macos_container.gateway_hosts import GATEWAY_HOSTNAME from bot_bottle.backend.macos_container.gateway_hosts import GATEWAY_HOSTNAME
from bot_bottle.backend.macos_container.launch import ( from bot_bottle.backend.macos_container.launch import (
_agent_run_argv, _agent_run_argv,
@@ -6,7 +6,7 @@ import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, Mock, patch from unittest.mock import MagicMock, Mock, patch
from bot_bottle.backend.macos_container.consolidated_launch import ( from bot_bottle.backend.macos_container.infra_launch import (
GatewayEndpoint, GatewayEndpoint,
ensure_gateway, ensure_gateway,
register_agent, register_agent,
@@ -16,7 +16,7 @@ from bot_bottle.egress import EgressPlan, EgressRoute
from bot_bottle.git_gate import GitGatePlan from bot_bottle.git_gate import GitGatePlan
from bot_bottle.orchestrator.client import RegisteredBottle from bot_bottle.orchestrator.client import RegisteredBottle
_MOD = "bot_bottle.backend.macos_container.consolidated_launch" _MOD = "bot_bottle.backend.macos_container.infra_launch"
_UTIL = "bot_bottle.backend.provision_bottle" _UTIL = "bot_bottle.backend.provision_bottle"
@@ -145,7 +145,7 @@ class TestLiveSourceIps(unittest.TestCase):
return [Mock(slug=s) for s in slugs] return [Mock(slug=s) for s in slugs]
def test_maps_slugs_to_container_addresses(self) -> None: def test_maps_slugs_to_container_addresses(self) -> None:
from bot_bottle.backend.macos_container.consolidated_launch import live_source_ips from bot_bottle.backend.macos_container.infra_launch import live_source_ips
with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \ with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \
patch(f"{_MOD}.container_mod.inspect_container_network_ip", patch(f"{_MOD}.container_mod.inspect_container_network_ip",
side_effect=["10.0.0.1", "10.0.0.2"]) as ip: side_effect=["10.0.0.1", "10.0.0.2"]) as ip:
@@ -156,7 +156,7 @@ class TestLiveSourceIps(unittest.TestCase):
def test_containers_without_an_address_are_skipped(self) -> None: def test_containers_without_an_address_are_skipped(self) -> None:
"""A container that hasn't been given a DHCP address yet contributes """A container that hasn't been given a DHCP address yet contributes
nothing the reap's grace window, not this list, protects it.""" nothing the reap's grace window, not this list, protects it."""
from bot_bottle.backend.macos_container.consolidated_launch import live_source_ips from bot_bottle.backend.macos_container.infra_launch import live_source_ips
with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \ with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \
patch(f"{_MOD}.container_mod.inspect_container_network_ip", patch(f"{_MOD}.container_mod.inspect_container_network_ip",
side_effect=["", "10.0.0.2"]): side_effect=["", "10.0.0.2"]):
@@ -165,7 +165,7 @@ class TestLiveSourceIps(unittest.TestCase):
def test_container_list_failure_raises(self) -> None: def test_container_list_failure_raises(self) -> None:
"""If container list fails, the live set is not authoritative and """If container list fails, the live set is not authoritative and
reconciliation must be skipped.""" reconciliation must be skipped."""
from bot_bottle.backend.macos_container.consolidated_launch import live_source_ips from bot_bottle.backend.macos_container.infra_launch import live_source_ips
from bot_bottle.backend.macos_container.enumerate import EnumerationError from bot_bottle.backend.macos_container.enumerate import EnumerationError
with patch(f"{_MOD}.enumerate_active", with patch(f"{_MOD}.enumerate_active",
side_effect=EnumerationError("container list failed")): side_effect=EnumerationError("container list failed")):
@@ -174,7 +174,7 @@ class TestLiveSourceIps(unittest.TestCase):
def test_per_container_inspect_failure_raises(self) -> None: def test_per_container_inspect_failure_raises(self) -> None:
"""If any individual inspect fails, the live set is not authoritative.""" """If any individual inspect fails, the live set is not authoritative."""
from bot_bottle.backend.macos_container.consolidated_launch import live_source_ips from bot_bottle.backend.macos_container.infra_launch import live_source_ips
from bot_bottle.backend.macos_container.enumerate import EnumerationError from bot_bottle.backend.macos_container.enumerate import EnumerationError
with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \ with patch(f"{_MOD}.enumerate_active", return_value=self._agents("a", "b")), \
patch(f"{_MOD}.container_mod.inspect_container_network_ip", patch(f"{_MOD}.container_mod.inspect_container_network_ip",