539234f29e
Compose-up has owned per-container lifecycle since PRD 0018 ch3;
the .start() / .stop() methods on DockerPipelockProxy /
DockerEgress / DockerGitGate / DockerSupervise (and their
abstractmethod declarations in the four base ABCs) were already
documented as vestigial. With the bundle path in flight
(PRD 0024 ch2), they are truly dead — collapse to nothing.
Changes:
- Removed start/stop methods from the four DockerSidecar
classes. Plan dataclasses, image/path constants,
container-name helpers, and the .prepare() methods all stay
(the renderer + apply path still need them).
- Removed the matching @abstractmethod declarations in the
base ABCs so concrete subclasses don't have to stub them.
- launch.launch() and prepare.resolve_plan() no longer take
proxy/git_gate/egress/supervise instance parameters. backend.py
loses the four instance attributes it threaded through.
prepare.resolve_plan() instantiates the four classes itself
to call their .prepare() methods.
- Deleted four integration tests that only exercised the
removed lifecycle: test_pipelock_sidecar_smoke,
test_supervise_sidecar, test_git_gate_sidecar,
test_git_gate_mirror.
- Dropped the .stop-idempotency case in test_orphan_cleanup;
the network-cleanup cases stay (those test real production
code).
- Marked test_pipelock_apply @skip pending chunk 4 — its
bringup helper used .start; chunk 4 rewrites it with direct
`docker run`.
Dockerfile deletion deferred to chunk 5 (when the bundle flag
default flips) — the legacy compose path still needs
Dockerfile.{egress,git-gate,supervise} until then.
Net: 708 lines removed, 80 added.
533 unit tests + 27 integration tests passing (5 skipped: the
chunk-4-pending case + existing GITEA_ACTIONS guards).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""Integration: the network-cleanup primitives the start-flow trap
|
|
depends on are idempotent. The original orphan-network bug was a
|
|
trap-ordering issue; the fix moved the install earlier. The trap
|
|
is only safe if network_remove is a no-op against missing
|
|
resources.
|
|
|
|
The PipelockProxy.stop idempotency case that used to live here was
|
|
removed in PRD 0024 chunk 3 when the per-container .stop method
|
|
went away — sidecar teardown is now compose's responsibility, and
|
|
`compose down` already no-ops on missing containers."""
|
|
|
|
import os
|
|
import subprocess
|
|
import unittest
|
|
|
|
from claude_bottle.backend.docker.network import (
|
|
network_create_egress,
|
|
network_create_internal,
|
|
network_remove,
|
|
)
|
|
from tests._docker import skip_unless_docker
|
|
|
|
|
|
@skip_unless_docker()
|
|
class TestOrphanCleanup(unittest.TestCase):
|
|
def setUp(self):
|
|
self.slug = f"cb-test-orphan-{os.getpid()}"
|
|
self.internal_name = ""
|
|
self.egress_name = ""
|
|
|
|
def tearDown(self):
|
|
for n in (self.internal_name, self.egress_name):
|
|
if n:
|
|
subprocess.run(
|
|
["docker", "network", "rm", n],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
check=False,
|
|
)
|
|
|
|
def test_remove_missing_is_noop(self):
|
|
# Returning True == idempotent success.
|
|
self.assertTrue(network_remove(f"claude-bottle-net-{self.slug}-does-not-exist"))
|
|
|
|
@unittest.skipIf(
|
|
os.environ.get("GITEA_ACTIONS") == "true",
|
|
"skipped under act_runner: docker socket mount topology breaks "
|
|
"in-process visibility of networks created on the host daemon",
|
|
)
|
|
def test_create_and_remove(self):
|
|
self.internal_name = network_create_internal(self.slug)
|
|
self.egress_name = network_create_egress(self.slug)
|
|
|
|
nets = subprocess.run(
|
|
["docker", "network", "ls", "--format", "{{.Name}}"],
|
|
capture_output=True, text=True, check=True,
|
|
).stdout.splitlines()
|
|
self.assertIn(self.internal_name, nets)
|
|
self.assertIn(self.egress_name, nets)
|
|
|
|
self.assertTrue(network_remove(self.internal_name))
|
|
self.assertTrue(network_remove(self.egress_name))
|
|
|
|
nets_after = subprocess.run(
|
|
["docker", "network", "ls", "--format", "{{.Name}}"],
|
|
capture_output=True, text=True, check=True,
|
|
).stdout.splitlines()
|
|
self.assertNotIn(self.internal_name, nets_after)
|
|
self.assertNotIn(self.egress_name, nets_after)
|
|
|
|
# Idempotent on already-removed.
|
|
self.assertTrue(network_remove(self.internal_name))
|
|
self.assertTrue(network_remove(self.egress_name))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|