refactor(dashboard): discover via docker compose ls
PRD 0018 chunk 5. The dashboard's operator-edit verbs
(`routes edit`, `pipelock edit`) enumerated running sidecars
via `docker ps --filter name=...` prefix scans. Switch to
`docker compose ls`-based discovery so the dashboard, cleanup
CLI, and launch step all agree on what's running.
Mechanics:
- `claude_bottle/backend/docker/compose.py` grows three shared
helpers: `list_compose_projects` (the JSON parse moved out
of cleanup), `slug_from_compose_project` (inverse of
`compose_project_name`), and `list_active_slugs` (sugar over
the first two for the common "what's running?" question).
- cleanup.py drops its private `_list_compose_projects` +
`_PROJECT_PREFIX` in favor of the shared ones; `list_active`
simplifies (one compose-ls call, not two).
- dashboard.py's `_discover_sidecar_slugs` becomes
`_discover_active_with_service`: cross-references the active
slug list with a label-filtered `docker ps` so only bottles
whose given service container is actually up surface in the
edit menu. Bottles without an egress sidecar (no
bottle.egress.routes) no longer appear for `routes edit`.
3 new unit tests cover the slug ↔ compose-project naming
contract; manual probe with a fake compose project confirms
both `discover_egress_slugs` and `discover_pipelock_slugs`
return the expected slug.
This commit is contained in:
@@ -14,7 +14,12 @@ from pathlib import Path
|
||||
|
||||
from claude_bottle.backend import BottleSpec
|
||||
from claude_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from claude_bottle.backend.docker.compose import bottle_plan_to_compose
|
||||
from claude_bottle.backend.docker.compose import (
|
||||
COMPOSE_PROJECT_PREFIX,
|
||||
bottle_plan_to_compose,
|
||||
compose_project_name,
|
||||
slug_from_compose_project,
|
||||
)
|
||||
from claude_bottle.egress import (
|
||||
EgressPlan,
|
||||
EgressRoute,
|
||||
@@ -450,5 +455,27 @@ class TestFullMatrix(unittest.TestCase):
|
||||
self.assertEqual(expected, set(s.keys()))
|
||||
|
||||
|
||||
class TestProjectNaming(unittest.TestCase):
|
||||
"""The slug ↔ compose-project mapping is the contract dashboard,
|
||||
cleanup, and launch all rely on. Lock it down."""
|
||||
|
||||
def test_compose_project_name_is_prefix_plus_slug(self):
|
||||
self.assertEqual(
|
||||
f"{COMPOSE_PROJECT_PREFIX}myagent-abc12",
|
||||
compose_project_name("myagent-abc12"),
|
||||
)
|
||||
|
||||
def test_slug_from_compose_project_is_inverse(self):
|
||||
self.assertEqual(
|
||||
"myagent-abc12",
|
||||
slug_from_compose_project(f"{COMPOSE_PROJECT_PREFIX}myagent-abc12"),
|
||||
)
|
||||
|
||||
def test_slug_from_unrelated_project_returns_empty(self):
|
||||
# Defends against `docker compose ls` including non-bottle
|
||||
# projects on a host with other compose setups.
|
||||
self.assertEqual("", slug_from_compose_project("other-project"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user