4fb0f64249
The first concrete LaunchBroker, proving the orchestrator -> backend seam
on the cheapest backend (the sidecar bundle is already containers):
* orchestrator/docker_broker.py — DockerBroker runs a container on a
verified launch (`docker run --detach --name <bottle> --label ...
<image_ref>`) and removes it on teardown (`docker rm --force`,
idempotent on an already-absent container). The argv is built only from
the request's static ids/flags, so nothing free-form reaches docker;
provenance/schema verification is inherited from LaunchBroker.submit.
* __main__.py gains `--broker {stub,docker}` so the harness can drive real
containers.
Slice 3 launches a single container from image_ref (the seam); the full
agent + sidecar bundle is a later slice.
Tests: unit (docker mocked) — argv from static fields, launch/teardown call
the right commands, missing-image and docker-failure raise, teardown
idempotent on missing, forged token never touches docker; integration
(gated on a reachable daemon) — launch creates a real container, teardown
removes it. Full suite green (only pre-existing /bin/sleep errors);
integration verified locally against real docker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""Integration: the Docker launch broker starts and removes a real container.
|
|
|
|
Gated on a reachable Docker daemon (skips cleanly otherwise). Uses a tiny
|
|
image; the container may exit immediately — we only assert it exists after
|
|
launch and is gone after teardown.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import secrets
|
|
import subprocess
|
|
import unittest
|
|
|
|
from bot_bottle.orchestrator.broker import LaunchRequest, sign_request
|
|
from bot_bottle.orchestrator.docker_broker import DockerBroker, container_name
|
|
from tests._docker import skip_unless_docker
|
|
|
|
IMAGE = "busybox"
|
|
|
|
|
|
@skip_unless_docker()
|
|
class TestDockerBrokerIntegration(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.secret = secrets.token_bytes(16)
|
|
self.broker = DockerBroker(self.secret)
|
|
self.bottle_id = "itest" + secrets.token_hex(4)
|
|
self.name = container_name(self.bottle_id)
|
|
self.addCleanup(
|
|
lambda: subprocess.run(
|
|
["docker", "rm", "--force", self.name],
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False,
|
|
)
|
|
)
|
|
|
|
def _exists(self) -> bool:
|
|
proc = subprocess.run(
|
|
["docker", "ps", "-a", "--filter", f"name=^{self.name}$",
|
|
"--format", "{{.Names}}"],
|
|
stdout=subprocess.PIPE, text=True, check=False,
|
|
)
|
|
return self.name in proc.stdout.split()
|
|
|
|
def _submit(self, op: str, **kw: str) -> None:
|
|
req = LaunchRequest(op=op, bottle_id=self.bottle_id, **kw)
|
|
self.broker.submit(sign_request(req, self.secret))
|
|
|
|
def test_launch_creates_then_teardown_removes(self) -> None:
|
|
self._submit("launch", image_ref=IMAGE)
|
|
self.assertTrue(self._exists(), "container should exist after launch")
|
|
self._submit("teardown")
|
|
self.assertFalse(self._exists(), "container should be gone after teardown")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|