refactor(docker): drop legacy per-sidecar container_name functions
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 41s

Same line of cleanup as the supervise rename: the per-sidecar
container names (`claude-bottle-pipelock-<slug>`,
`claude-bottle-egress-<slug>`, `claude-bottle-git-gate-<slug>`)
were docker-network aliases pointing at the bundle, kept so legacy
URLs would keep resolving. Replaces them with short hostnames
(`pipelock`, `egress`, `git-gate`) matching the existing
`EGRESS_HOSTNAME` pattern, and inlines the bundle-loopback URL
(`http://127.0.0.1:8888`) for the in-bundle egress→pipelock hop —
matching what smolmachines already does.

Drops the three `*_container_name` functions, `pipelock_proxy_url`,
and `git_gate_host`. Their callers move to the new constants:
- `PIPELOCK_HOSTNAME = "pipelock"` (claude_bottle/pipelock.py)
- `GIT_GATE_HOSTNAME = "git-gate"` (claude_bottle/git_gate.py)
- `BUNDLE_LOCAL_PIPELOCK_URL` (backend/docker/pipelock.py)

The agent's HTTP_PROXY now reads `http://pipelock:8888` (vs the
old `http://claude-bottle-pipelock-<slug>:8888`); the gitconfig
insteadOf rewrites become `git://git-gate/<repo>.git`. The prepare-
time orphan probe is collapsed onto the bundle container name
(`claude-bottle-sidecars-<slug>`) instead of the four legacy
per-sidecar names that no backend creates anymore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:04:48 -04:00
parent 8ecba2b458
commit 727f30d422
13 changed files with 67 additions and 105 deletions
+8 -12
View File
@@ -49,8 +49,9 @@ from ...egress import (
EGRESS_HOSTNAME, EGRESS_HOSTNAME,
EGRESS_ROUTES_IN_CONTAINER, EGRESS_ROUTES_IN_CONTAINER,
) )
from ...git_gate import GIT_GATE_HOSTNAME, git_gate_aggregate_extra_hosts
from ...log import die, warn from ...log import die, warn
from ...git_gate import git_gate_aggregate_extra_hosts from ...pipelock import PIPELOCK_HOSTNAME
from ...supervise import ( from ...supervise import (
CURRENT_CONFIG_DIR_IN_AGENT, CURRENT_CONFIG_DIR_IN_AGENT,
QUEUE_DIR_IN_CONTAINER, QUEUE_DIR_IN_CONTAINER,
@@ -62,20 +63,17 @@ from .bottle_plan import DockerBottlePlan
from .egress import ( from .egress import (
EGRESS_CA_IN_CONTAINER, EGRESS_CA_IN_CONTAINER,
EGRESS_PIPELOCK_CA_IN_CONTAINER, EGRESS_PIPELOCK_CA_IN_CONTAINER,
egress_container_name,
) )
from .git_gate import ( from .git_gate import (
GIT_GATE_ACCESS_HOOK_IN_CONTAINER, GIT_GATE_ACCESS_HOOK_IN_CONTAINER,
GIT_GATE_CREDS_DIR_IN_CONTAINER, GIT_GATE_CREDS_DIR_IN_CONTAINER,
GIT_GATE_ENTRYPOINT_IN_CONTAINER, GIT_GATE_ENTRYPOINT_IN_CONTAINER,
GIT_GATE_HOOK_IN_CONTAINER, GIT_GATE_HOOK_IN_CONTAINER,
git_gate_container_name,
) )
from .pipelock import ( from .pipelock import (
PIPELOCK_CA_CERT_IN_CONTAINER, PIPELOCK_CA_CERT_IN_CONTAINER,
PIPELOCK_CA_KEY_IN_CONTAINER, PIPELOCK_CA_KEY_IN_CONTAINER,
PIPELOCK_PORT, PIPELOCK_PORT,
pipelock_container_name,
) )
from .provision.ca import AGENT_CA_BUNDLE, AGENT_CA_PATH from .provision.ca import AGENT_CA_BUNDLE, AGENT_CA_PATH
from .sidecar_bundle import ( from .sidecar_bundle import (
@@ -232,17 +230,15 @@ def _sidecar_bundle_service(plan: DockerBottlePlan) -> dict[str, Any]:
"read_only": False, "read_only": False,
}) })
# Internal-network aliases: every shortname + long-form legacy # Internal-network aliases: the agent reaches each daemon through
# name routes to the bundle so the agent's HTTPS_PROXY URL # its short name (pipelock / egress / git-gate / supervise) which
# (which references either `pipelock` or `egress`) keeps # the bundle answers as if it were the daemon itself.
# resolving without an agent-side change.
internal_aliases = [ internal_aliases = [
pipelock_container_name(plan.slug), PIPELOCK_HOSTNAME,
EGRESS_HOSTNAME, EGRESS_HOSTNAME,
egress_container_name(plan.slug),
] ]
if gp.upstreams: if gp.upstreams:
internal_aliases.append(git_gate_container_name(plan.slug)) internal_aliases.append(GIT_GATE_HOSTNAME)
if sp is not None: if sp is not None:
internal_aliases.append(SUPERVISE_HOSTNAME) internal_aliases.append(SUPERVISE_HOSTNAME)
@@ -328,7 +324,7 @@ def _agent_proxy_url(plan: DockerBottlePlan) -> str:
if plan.egress_plan.routes: if plan.egress_plan.routes:
from .egress import EGRESS_PORT from .egress import EGRESS_PORT
return f"http://{EGRESS_HOSTNAME}:{EGRESS_PORT}" return f"http://{EGRESS_HOSTNAME}:{EGRESS_PORT}"
return f"http://{pipelock_container_name(plan.slug)}:{PIPELOCK_PORT}" return f"http://{PIPELOCK_HOSTNAME}:{PIPELOCK_PORT}"
def _agent_no_proxy(plan: DockerBottlePlan) -> str: def _agent_no_proxy(plan: DockerBottlePlan) -> str:
-8
View File
@@ -32,14 +32,6 @@ EGRESS_PIPELOCK_CA_IN_CONTAINER = (
) )
def egress_container_name(slug: str) -> str:
"""The legacy per-sidecar container name. Kept as a function so
the renderer can register it as a docker-network alias on the
bundle — any code still referring to `claude-bottle-egress-<slug>`
resolves to the bundle's IP."""
return f"claude-bottle-egress-{slug}"
def egress_tls_init(stage_dir: Path) -> tuple[Path, Path]: def egress_tls_init(stage_dir: Path) -> tuple[Path, Path]:
"""Mint the per-bottle egress MITM CA via host `openssl req`. """Mint the per-bottle egress MITM CA via host `openssl req`.
+5 -21
View File
@@ -1,9 +1,8 @@
"""Docker-side git-gate helpers: in-container paths the renderer's """Docker-side git-gate constants: in-container paths the renderer's
bind-mounts target, port pin, and container naming. The bind-mounts target + the listening port. The prepare-time entrypoint
prepare-time entrypoint/hook render lives on the platform-neutral / hook render lives on the platform-neutral `GitGate` ABC — backends
`GitGate` ABC — backends instantiate it directly. The git-gate instantiate it directly. The git-gate daemon's container lifecycle
daemon's container lifecycle is owned by the sidecar bundle is owned by the sidecar bundle (PRD 0024)."""
(PRD 0024)."""
from __future__ import annotations from __future__ import annotations
@@ -15,18 +14,3 @@ GIT_GATE_CREDS_DIR_IN_CONTAINER = "/git-gate/creds"
# git daemon's default listening port. # git daemon's default listening port.
GIT_GATE_PORT = 9418 GIT_GATE_PORT = 9418
def git_gate_container_name(slug: str) -> str:
"""The legacy per-sidecar container name. Kept as a function so
the renderer can register it as a docker-network alias on the
bundle — any code still dialing `claude-bottle-git-gate-<slug>`
resolves to the bundle's IP."""
return f"claude-bottle-git-gate-{slug}"
def git_gate_host(slug: str) -> str:
"""The hostname the agent's git client connects to. Resolves via
the bundle's network alias to the bundle container, where the
git-gate daemon listens on GIT_GATE_PORT."""
return git_gate_container_name(slug)
+2 -2
View File
@@ -65,7 +65,7 @@ from .compose import (
) )
from .egress import egress_tls_init from .egress import egress_tls_init
from .pipelock import ( from .pipelock import (
pipelock_proxy_url, BUNDLE_LOCAL_PIPELOCK_URL,
pipelock_tls_init, pipelock_tls_init,
) )
@@ -149,7 +149,7 @@ def launch(
mitmproxy_ca_host_path=egress_ca_host, mitmproxy_ca_host_path=egress_ca_host,
mitmproxy_ca_cert_only_host_path=egress_ca_cert_only, mitmproxy_ca_cert_only_host_path=egress_ca_cert_only,
pipelock_ca_host_path=ca_cert_host, pipelock_ca_host_path=ca_cert_host,
pipelock_proxy_url=pipelock_proxy_url(plan.slug), pipelock_proxy_url=BUNDLE_LOCAL_PIPELOCK_URL,
) )
supervise_plan = plan.supervise_plan supervise_plan = plan.supervise_plan
if supervise_plan is not None: if supervise_plan is not None:
+5 -6
View File
@@ -35,12 +35,11 @@ PIPELOCK_IMAGE = os.environ.get(
PIPELOCK_PORT = os.environ.get("CLAUDE_BOTTLE_PIPELOCK_PORT", "8888") PIPELOCK_PORT = os.environ.get("CLAUDE_BOTTLE_PIPELOCK_PORT", "8888")
def pipelock_container_name(slug: str) -> str: # The URL egress dials for its upstream HTTPS_PROXY. egress and
return f"claude-bottle-pipelock-{slug}" # pipelock share the same container's network namespace inside the
# sidecar bundle, so loopback reaches pipelock directly — no docker
# DNS aliases involved.
def pipelock_proxy_url(slug: str) -> str: BUNDLE_LOCAL_PIPELOCK_URL = f"http://127.0.0.1:{PIPELOCK_PORT}"
return f"http://{pipelock_container_name(slug)}:{PIPELOCK_PORT}"
def pipelock_tls_init(stage_dir: Path) -> tuple[Path, Path]: def pipelock_tls_init(stage_dir: Path) -> tuple[Path, Path]:
+13 -25
View File
@@ -23,8 +23,6 @@ from ...supervise import Supervise
from .. import BottleSpec from .. import BottleSpec
from . import util as docker_mod from . import util as docker_mod
from .bottle_plan import DockerBottlePlan from .bottle_plan import DockerBottlePlan
from .egress import egress_container_name
from .git_gate import git_gate_container_name
from .bottle_state import ( from .bottle_state import (
BottleMetadata, BottleMetadata,
agent_state_dir, agent_state_dir,
@@ -39,7 +37,7 @@ from .bottle_state import (
supervise_state_dir, supervise_state_dir,
write_metadata, write_metadata,
) )
from .pipelock import pipelock_container_name from .sidecar_bundle import sidecar_bundle_container_name
def resolve_plan( def resolve_plan(
@@ -126,28 +124,18 @@ def resolve_plan(
f"clean up old containers with 'docker rm -f <name>'" f"clean up old containers with 'docker rm -f <name>'"
) )
# Probe sidecar container names for orphans from a previous run. # Probe the sidecar-bundle container name for an orphan from a
# Sidecar names are deterministic from the slug; an orphan would # previous run. Otherwise a stale bundle surfaces as a
# surface as a docker-create conflict deep inside launch() with no # docker-create conflict deep inside launch() with no actionable
# actionable hint. Fail fast here with a cleanup pointer instead. # hint; failing fast here points at the cleanup command.
# Only probe sidecars this launch will actually try to create: bundle_name = sidecar_bundle_container_name(slug)
# pipelock always; git-gate when bottle.git is non-empty; if docker_mod.container_exists(bundle_name):
# egress when bottle.egress.routes is non-empty. die(
sidecar_probes: list[tuple[str, str]] = [ f"sidecar bundle container '{bundle_name}' already exists. "
("pipelock", pipelock_container_name(slug)), f"This is an orphan from a previous run; clean it up with "
] f"'./cli.py cleanup' (or 'docker rm -f {bundle_name}') and "
if bottle.git: f"retry."
sidecar_probes.append(("git-gate", git_gate_container_name(slug))) )
if bottle.egress.routes:
sidecar_probes.append(("egress", egress_container_name(slug)))
for label, sidecar_name in sidecar_probes:
if docker_mod.container_exists(sidecar_name):
die(
f"{label} sidecar container '{sidecar_name}' already exists. "
f"This is an orphan from a previous run; clean it up with "
f"'./cli.py cleanup' (or 'docker rm -f {sidecar_name}') and "
f"retry."
)
# PRD 0018 chunk 2: prepare-time scratch files live under # PRD 0018 chunk 2: prepare-time scratch files live under
# ~/.claude-bottle/state/<slug>/<service>/ so chunk 3's compose # ~/.claude-bottle/state/<slug>/<service>/ so chunk 3's compose
@@ -19,11 +19,11 @@ import os
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from ....git_gate import GIT_GATE_HOSTNAME
from ....log import info from ....log import info
from ....manifest import GitEntry from ....manifest import GitEntry
from .. import util as docker_mod from .. import util as docker_mod
from ..bottle_plan import DockerBottlePlan from ..bottle_plan import DockerBottlePlan
from ..git_gate import git_gate_host
def provision_git(plan: DockerBottlePlan, target: str) -> None: def provision_git(plan: DockerBottlePlan, target: str) -> None:
@@ -56,7 +56,7 @@ def _provision_cwd_git(plan: DockerBottlePlan, target: str) -> None:
) )
def render_git_gate_gitconfig(slug: str, entries: tuple[GitEntry, ...]) -> str: def render_git_gate_gitconfig(entries: tuple[GitEntry, ...]) -> str:
"""Render the ~/.gitconfig content for git-gate `insteadOf` """Render the ~/.gitconfig content for git-gate `insteadOf`
rewrites. Pure host-side, no docker; exposed for tests. rewrites. Pure host-side, no docker; exposed for tests.
@@ -64,7 +64,6 @@ def render_git_gate_gitconfig(slug: str, entries: tuple[GitEntry, ...]) -> str:
cleanly without conditional formatting at the call site.""" cleanly without conditional formatting at the call site."""
if not entries: if not entries:
return "" return ""
gate = git_gate_host(slug)
out = [ out = [
"# claude-bottle git-gate (PRD 0008): every git operation against\n", "# claude-bottle git-gate (PRD 0008): every git operation against\n",
"# a declared upstream routes through the gate, which mirrors\n", "# a declared upstream routes through the gate, which mirrors\n",
@@ -72,7 +71,7 @@ def render_git_gate_gitconfig(slug: str, entries: tuple[GitEntry, ...]) -> str:
"# fetch-from-upstream-before-every-upload-pack via access-hook).\n", "# fetch-from-upstream-before-every-upload-pack via access-hook).\n",
] ]
for entry in entries: for entry in entries:
out.append(f'[url "git://{gate}/{entry.Name}.git"]\n') out.append(f'[url "git://{GIT_GATE_HOSTNAME}/{entry.Name}.git"]\n')
out.append(f"\tinsteadOf = {entry.Upstream}\n") out.append(f"\tinsteadOf = {entry.Upstream}\n")
return "".join(out) return "".join(out)
@@ -87,7 +86,7 @@ def _provision_git_gate_config(plan: DockerBottlePlan, target: str) -> None:
container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node") container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node")
container_gitconfig = f"{container_home}/.gitconfig" container_gitconfig = f"{container_home}/.gitconfig"
content = render_git_gate_gitconfig(plan.slug, bottle.git) content = render_git_gate_gitconfig(bottle.git)
config_file = plan.stage_dir / "agent_gitconfig" config_file = plan.stage_dir / "agent_gitconfig"
config_file.write_text(content) config_file.write_text(content)
config_file.chmod(0o600) config_file.chmod(0o600)
+2 -8
View File
@@ -42,19 +42,13 @@ from ..docker.git_gate import (
GIT_GATE_ENTRYPOINT_IN_CONTAINER, GIT_GATE_ENTRYPOINT_IN_CONTAINER,
GIT_GATE_HOOK_IN_CONTAINER, GIT_GATE_HOOK_IN_CONTAINER,
) )
from ..docker.pipelock import pipelock_tls_init from ..docker.pipelock import BUNDLE_LOCAL_PIPELOCK_URL, pipelock_tls_init
from . import sidecar_bundle as _bundle from . import sidecar_bundle as _bundle
from . import smolvm as _smolvm from . import smolvm as _smolvm
from .bottle import SmolmachinesBottle from .bottle import SmolmachinesBottle
from .bottle_plan import SmolmachinesBottlePlan from .bottle_plan import SmolmachinesBottlePlan
# Pipelock's upstream when egress is in the bundle: localhost on
# the bundle's own loopback. No docker DNS aliases involved —
# pipelock + egress share the same container's network namespace.
_BUNDLE_LOCAL_PIPELOCK_URL = "http://127.0.0.1:8888"
@contextmanager @contextmanager
def launch( def launch(
plan: SmolmachinesBottlePlan, plan: SmolmachinesBottlePlan,
@@ -95,7 +89,7 @@ def launch(
# On smolmachines, egress's upstream is pipelock # On smolmachines, egress's upstream is pipelock
# on the bundle's localhost — they're in the same # on the bundle's localhost — they're in the same
# container's network namespace. # container's network namespace.
pipelock_proxy_url=_BUNDLE_LOCAL_PIPELOCK_URL, pipelock_proxy_url=BUNDLE_LOCAL_PIPELOCK_URL,
) )
plan = dataclasses.replace( plan = dataclasses.replace(
plan, proxy_plan=proxy_plan, egress_plan=egress_plan, plan, proxy_plan=proxy_plan, egress_plan=egress_plan,
+5
View File
@@ -38,6 +38,11 @@ from .log import die
from .manifest import Bottle from .manifest import Bottle
# Short network alias for git-gate inside the sidecar bundle. The
# agent's `.gitconfig` insteadOf rewrites resolve through this name.
GIT_GATE_HOSTNAME = "git-gate"
def _empty_str_map() -> dict[str, str]: def _empty_str_map() -> dict[str, str]:
return {} return {}
+10 -4
View File
@@ -55,6 +55,12 @@ PIPELOCK_CA_CERT_IN_CONTAINER = "/etc/pipelock-ca.pem"
PIPELOCK_CA_KEY_IN_CONTAINER = "/etc/pipelock-ca-key.pem" PIPELOCK_CA_KEY_IN_CONTAINER = "/etc/pipelock-ca-key.pem"
# Short network alias for pipelock inside the sidecar bundle. The
# agent's HTTP_PROXY (when no egress is declared) and any in-bundle
# consumer's URL both reference this name.
PIPELOCK_HOSTNAME = "pipelock"
# --- Allowlist resolution -------------------------------------------------- # --- Allowlist resolution --------------------------------------------------
@@ -329,10 +335,10 @@ class PipelockProxy:
`slug` is the agent-derived identifier (lowercased, `slug` is the agent-derived identifier (lowercased,
hyphen-normalized) used as the suffix in every per-agent hyphen-normalized) used as the suffix in every per-agent
resource name — the agent container, the pipelock container resource name — the agent container, the sidecar bundle
(`claude-bottle-pipelock-<slug>`), the internal/egress container, the internal/egress networks. It's stored on the
networks. It's stored on the returned plan so the backend's returned plan so the backend's launch step can derive those
launch step can derive the sidecar's container name. names.
The CA paths the YAML references are the module-level The CA paths the YAML references are the module-level
in-container constants. The host-side counterparts are in-container constants. The host-side counterparts are
+1 -1
View File
@@ -402,7 +402,7 @@ class TestSandboxEscape(unittest.TestCase):
("aws", "TEST_SECRET_AWS"), ("aws", "TEST_SECRET_AWS"),
("generic", "TEST_SECRET_GENERIC"), ("generic", "TEST_SECRET_GENERIC"),
] ]
gate_host = f"claude-bottle-git-gate-{self._identity}" gate_host = "git-gate"
for name, var in shapes: for name, var in shapes:
with self.subTest(secret=name): with self.subTest(secret=name):
+6 -7
View File
@@ -112,7 +112,7 @@ def _egress_plan(routes: tuple[EgressRoute, ...] = ()) -> EgressPlan:
mitmproxy_ca_host_path=STATE / "egress-ca" / "mitmproxy-ca.pem", mitmproxy_ca_host_path=STATE / "egress-ca" / "mitmproxy-ca.pem",
mitmproxy_ca_cert_only_host_path=STATE / "egress-ca" / "ca.pem", mitmproxy_ca_cert_only_host_path=STATE / "egress-ca" / "ca.pem",
pipelock_ca_host_path=STATE / "pipelock-ca" / "ca.pem", pipelock_ca_host_path=STATE / "pipelock-ca" / "ca.pem",
pipelock_proxy_url=f"http://claude-bottle-pipelock-{SLUG}:8888", pipelock_proxy_url="http://127.0.0.1:8888",
) )
@@ -221,7 +221,7 @@ class TestAgentAlwaysPresent(unittest.TestCase):
proxy_lines = [e for e in env if e.startswith("HTTPS_PROXY=")] proxy_lines = [e for e in env if e.startswith("HTTPS_PROXY=")]
self.assertEqual(1, len(proxy_lines)) self.assertEqual(1, len(proxy_lines))
self.assertEqual( self.assertEqual(
f"HTTPS_PROXY=http://claude-bottle-pipelock-{SLUG}:8888", "HTTPS_PROXY=http://pipelock:8888",
proxy_lines[0], proxy_lines[0],
) )
@@ -304,12 +304,11 @@ class TestSidecarBundleShape(unittest.TestCase):
def test_internal_aliases_cover_pipelock_and_egress_shortnames(self): def test_internal_aliases_cover_pipelock_and_egress_shortnames(self):
# The agent's HTTPS_PROXY url references either `egress` or # The agent's HTTPS_PROXY url references either `egress` or
# `pipelock` (long form). Both must resolve to the bundle. # `pipelock`. Both must resolve to the bundle.
sc = self._render()["services"]["sidecars"] sc = self._render()["services"]["sidecars"]
aliases = set(sc["networks"]["internal"]["aliases"]) aliases = set(sc["networks"]["internal"]["aliases"])
self.assertIn("egress", aliases) self.assertIn("egress", aliases)
self.assertIn(f"claude-bottle-pipelock-{SLUG}", aliases) self.assertIn("pipelock", aliases)
self.assertIn(f"claude-bottle-egress-{SLUG}", aliases)
def test_internal_aliases_omit_inactive_sidecars(self): def test_internal_aliases_omit_inactive_sidecars(self):
# With no git-gate / supervise, those names are NOT aliased # With no git-gate / supervise, those names are NOT aliased
@@ -317,13 +316,13 @@ class TestSidecarBundleShape(unittest.TestCase):
# listening inside the bundle. # listening inside the bundle.
sc = self._render()["services"]["sidecars"] sc = self._render()["services"]["sidecars"]
aliases = set(sc["networks"]["internal"]["aliases"]) aliases = set(sc["networks"]["internal"]["aliases"])
self.assertNotIn(f"claude-bottle-git-gate-{SLUG}", aliases) self.assertNotIn("git-gate", aliases)
self.assertNotIn("supervise", aliases) self.assertNotIn("supervise", aliases)
def test_internal_aliases_include_active_sidecars(self): def test_internal_aliases_include_active_sidecars(self):
sc = self._render(with_git=True, supervise=True)["services"]["sidecars"] sc = self._render(with_git=True, supervise=True)["services"]["sidecars"]
aliases = set(sc["networks"]["internal"]["aliases"]) aliases = set(sc["networks"]["internal"]["aliases"])
self.assertIn(f"claude-bottle-git-gate-{SLUG}", aliases) self.assertIn("git-gate", aliases)
self.assertIn("supervise", aliases) self.assertIn("supervise", aliases)
def test_daemons_csv_lists_only_active(self): def test_daemons_csv_lists_only_active(self):
+6 -6
View File
@@ -9,15 +9,15 @@ from tests.fixtures import fixture_minimal, fixture_with_git
class TestGitGateGitconfigRender(unittest.TestCase): class TestGitGateGitconfigRender(unittest.TestCase):
def test_empty_entries_renders_nothing(self): def test_empty_entries_renders_nothing(self):
bottle = fixture_minimal().bottles["dev"] bottle = fixture_minimal().bottles["dev"]
self.assertEqual("", render_git_gate_gitconfig("demo", bottle.git)) self.assertEqual("", render_git_gate_gitconfig(bottle.git))
def test_one_block_per_entry(self): def test_one_block_per_entry(self):
bottle = fixture_with_git().bottles["dev"] bottle = fixture_with_git().bottles["dev"]
out = render_git_gate_gitconfig("demo", bottle.git) out = render_git_gate_gitconfig(bottle.git)
# Both entries map to a [url ...] block keyed on the gate's # Both entries map to a [url ...] block keyed on the gate's
# container hostname (claude-bottle-git-gate-<slug>). # short network alias (`git-gate`) inside the sidecar bundle.
self.assertIn( self.assertIn(
'[url "git://claude-bottle-git-gate-demo/claude-bottle.git"]', '[url "git://git-gate/claude-bottle.git"]',
out, out,
) )
self.assertIn( self.assertIn(
@@ -25,7 +25,7 @@ class TestGitGateGitconfigRender(unittest.TestCase):
"ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git", "ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
out, out,
) )
self.assertIn('[url "git://claude-bottle-git-gate-demo/foo.git"]', out) self.assertIn('[url "git://git-gate/foo.git"]', out)
self.assertIn( self.assertIn(
"\tinsteadOf = ssh://git@github.com/didericis/foo.git", "\tinsteadOf = ssh://git@github.com/didericis/foo.git",
out, out,
@@ -37,7 +37,7 @@ class TestGitGateGitconfigRender(unittest.TestCase):
# gate push and leave fetch on the original URL — exactly the # gate push and leave fetch on the original URL — exactly the
# v1 design we've moved past. # v1 design we've moved past.
bottle = fixture_with_git().bottles["dev"] bottle = fixture_with_git().bottles["dev"]
out = render_git_gate_gitconfig("demo", bottle.git) out = render_git_gate_gitconfig(bottle.git)
self.assertIn("\tinsteadOf", out) self.assertIn("\tinsteadOf", out)
self.assertNotIn("pushInsteadOf", out) self.assertNotIn("pushInsteadOf", out)