refactor(sidecars): drop vestigial start/stop methods (PRD 0024 chunk 3)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 41s

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>
This commit is contained in:
2026-05-27 01:01:10 -04:00
parent c37344608b
commit 539234f29e
18 changed files with 80 additions and 1758 deletions
+4 -169
View File
@@ -5,17 +5,9 @@ step (upstream lift + entrypoint/hook render) from `GitGate`."""
from __future__ import annotations
import os
import subprocess
from pathlib import Path
from ...git_gate import (
GitGate,
GitGatePlan,
git_gate_aggregate_extra_hosts,
git_gate_known_hosts_line,
)
from ...log import die, info, warn
from ...util import expand_tilde
from ...git_gate import GitGate
from . import util as docker_mod
@@ -59,163 +51,6 @@ def build_git_gate_image() -> None:
class DockerGitGate(GitGate):
"""Brings the git-gate sidecar up and down via Docker."""
def start(self, plan: GitGatePlan) -> str:
"""Boot the gate sidecar:
1. Build the gate image (no-op when cache is hot).
2. `docker create` on the internal network with the canonical
name; the image's ENTRYPOINT runs the cp'd entrypoint
script at start time.
3. `docker cp` the entrypoint, the shared pre-receive hook,
and each upstream's identity + known_hosts into the
container.
4. Attach to the per-agent egress network so the gate can
reach the real upstream.
5. `docker start`.
Returns the container name (the target passed to `.stop`)."""
if not plan.upstreams:
die("DockerGitGate.start called with no upstreams; caller should skip")
if not plan.internal_network or not plan.egress_network:
die(
"DockerGitGate.start: internal_network / egress_network must be "
"populated on the plan before start"
)
if not plan.entrypoint_script.is_file():
die(
f"git-gate entrypoint missing at {plan.entrypoint_script}; "
f"GitGate.prepare must run first"
)
if not plan.hook_script.is_file():
die(
f"git-gate hook missing at {plan.hook_script}; "
f"GitGate.prepare must run first"
)
if not plan.access_hook_script.is_file():
die(
f"git-gate access-hook missing at {plan.access_hook_script}; "
f"GitGate.prepare must run first"
)
build_git_gate_image()
name = git_gate_container_name(plan.slug)
info(f"starting git-gate sidecar {name} on network {plan.internal_network}")
create_args = [
"docker", "create",
"--name", name,
"--network", plan.internal_network,
]
for host, ip in git_gate_aggregate_extra_hosts(plan.upstreams).items():
create_args.extend(["--add-host", f"{host}:{ip}"])
create_args.append(GIT_GATE_IMAGE)
create_result = subprocess.run(
create_args, capture_output=True, text=True, check=False,
)
if create_result.returncode != 0:
die(
f"failed to create git-gate sidecar {name}: "
f"{create_result.stderr.strip()}"
)
# Order matters: entrypoint + hook first so they're present
# when docker start fires. Per-upstream creds afterwards.
stage_dir = plan.entrypoint_script.parent
cps: list[tuple[str, str, str]] = [
(str(plan.entrypoint_script), GIT_GATE_ENTRYPOINT_IN_CONTAINER, "entrypoint"),
(str(plan.hook_script), GIT_GATE_HOOK_IN_CONTAINER, "pre-receive hook"),
(str(plan.access_hook_script), GIT_GATE_ACCESS_HOOK_IN_CONTAINER, "access-hook"),
]
for u in plan.upstreams:
keypath = expand_tilde(u.identity_file)
cps.append((
keypath,
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-key",
f"upstream key for '{u.name}'",
))
if u.known_host_key:
hosts_path = stage_dir / f"git_gate_known_hosts_{u.name}"
hosts_path.write_text(
git_gate_known_hosts_line(
u.upstream_host, u.upstream_port, u.known_host_key
)
)
hosts_path.chmod(0o600)
cps.append((
str(hosts_path),
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-known_hosts",
f"upstream known_hosts for '{u.name}'",
))
for src, dst, label in cps:
cp_result = subprocess.run(
["docker", "cp", src, f"{name}:{dst}"],
capture_output=True,
text=True,
check=False,
)
if cp_result.returncode != 0:
subprocess.run(
["docker", "rm", "-f", name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
die(
f"failed to copy {label} into {name}: "
f"{cp_result.stderr.strip()}"
)
connect_result = subprocess.run(
["docker", "network", "connect", plan.egress_network, name],
capture_output=True, text=True, check=False,
)
if connect_result.returncode != 0:
subprocess.run(
["docker", "rm", "-f", name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
die(
f"failed to attach git-gate sidecar {name} to egress network "
f"{plan.egress_network}: {connect_result.stderr.strip()}"
)
start_result = subprocess.run(
["docker", "start", name], capture_output=True, text=True, check=False,
)
if start_result.returncode != 0:
subprocess.run(
["docker", "rm", "-f", name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
die(
f"failed to start git-gate sidecar {name}: "
f"{start_result.stderr.strip()}"
)
return name
def stop(self, target: str) -> None:
"""Idempotent: missing container is success. `target` is the
container name returned by `.start`."""
if subprocess.run(
["docker", "inspect", target],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
).returncode == 0:
if subprocess.run(
["docker", "rm", "-f", target],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
).returncode != 0:
warn(
f"failed to remove git-gate sidecar {target}; "
f"clean up with 'docker rm -f {target}'"
)
"""Docker-flavored GitGate: inherits `.prepare()` from the base.
Container lifecycle is owned by compose; per-container
`.start()` / `.stop()` were removed in PRD 0024 chunk 3."""