mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(runtime): stamp spawned containers with the stack's own compose-project labels
Agent spawns (all five provider paths), intake/secretary chats, and sandbox sidecars now carry com.docker.compose.project/service/oneoff/ config-hash labels copied from the orchestrator's own compose project, so a Docker UI (UGOS) groups them under the stack's project and they die with the stack: bare compose stop/restart affects them, compose down removes them (the config-hash label must be PRESENT for down to even see the container — compose filters its API listing on that key before the orphan predicate runs, verified live), and up -d deliberately does not resurrect them since the orchestrator respawns its own agents. Self-discovery reads the orchestrator's own container id from /proc/self/mountinfo keyed on the root-independent /containers/<id>/ segment — the UGREEN NAS data-root is /volume1/@docker on btrfs, so its mountinfo reads /@docker/containers/..., never the textbook /var/lib/docker path (verified against the live NAS) — with a HOSTNAME short-id fallback, then one docker inspect cached per process. Only definitive outcomes cache; a transient inspect failure logs and retries on the next spawn. Outside compose the helper yields nothing and every spawn command is byte-for-byte unchanged.
This commit is contained in:
@@ -789,6 +789,54 @@ class TestSpawnIntakeSession:
|
||||
# The cloned cwd reached the docker cmd.
|
||||
assert "ROBOCO_WORKSPACE=/data/workspaces/roboco/board/intake-1" in run_calls[0]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spawn_adds_compose_labels_before_image(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""When the orchestrator resolves its own compose project, the
|
||||
persistent intake container carries it too — spliced in right before
|
||||
the trailing image element (docker run flags must precede the
|
||||
image)."""
|
||||
orch = _make_minimal_orchestrator()
|
||||
run_calls: list[list[str]] = []
|
||||
_wire_spawn_mocks(monkeypatch, orch, run_calls)
|
||||
|
||||
async def _fake_label_args(service: str) -> list[str]:
|
||||
return ["--label", f"com.docker.compose.service={service}"]
|
||||
|
||||
monkeypatch.setattr(
|
||||
"roboco.runtime.orchestrator.compose_label_args", _fake_label_args
|
||||
)
|
||||
|
||||
await orch.spawn_intake_session("sess-labels", project_slug="roboco")
|
||||
|
||||
assert len(run_calls) == 1
|
||||
cmd = run_calls[0]
|
||||
assert cmd[-3] == "--label"
|
||||
assert cmd[-2] == f"com.docker.compose.service={INTAKE_AGENT_ID}"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spawn_omits_compose_labels_outside_compose(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""The real helper returns [] outside a compose stack — the cmd
|
||||
shape (image last) is byte-for-byte unchanged from today."""
|
||||
orch = _make_minimal_orchestrator()
|
||||
run_calls: list[list[str]] = []
|
||||
_wire_spawn_mocks(monkeypatch, orch, run_calls)
|
||||
|
||||
async def _no_labels(_service: str) -> list[str]:
|
||||
return []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"roboco.runtime.orchestrator.compose_label_args", _no_labels
|
||||
)
|
||||
|
||||
await orch.spawn_intake_session("sess-no-labels", project_slug="roboco")
|
||||
|
||||
assert len(run_calls) == 1
|
||||
assert not any(a.startswith("com.docker.compose.") for a in run_calls[0])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scope_must_be_exactly_one(self) -> None:
|
||||
orch = _make_minimal_orchestrator()
|
||||
|
||||
Reference in New Issue
Block a user