Files
bot-bottle/bot_bottle/backend/docker/network.py
didericis-claude a59da9921e
lint / lint (push) Failing after 1m26s
test / unit (pull_request) Failing after 35s
test / integration (pull_request) Successful in 44s
chore: remove all pipelock references from tests, docs, and non-pipelock source
- Strip pipelock from all unit and integration test fixtures:
  proxy_plan fields removed from DockerBottlePlan/SmolmachinesBottlePlan
  constructors; pipelock-specific test classes deleted or renamed
- Update test_sidecar_init: remove test_pipelock_loses_egress_tokens,
  rename "pipelock" daemon fixtures to "git-gate" throughout
- Remove test_pipelock_binary_present_and_versioned from integration test
- Remove test_pipelock_answers_on_bundle_ip from smolmachines launch test
- Update _SANDBOX_BLOCK_MARKERS: remove "pipelock" marker (egress blocks)
- Dockerfile.sidecars: remove pipelock build stage and COPY; update layout
  comments and port table
- egress_entrypoint.sh: update comments now that egress is sole proxy
- Clean up pipelock references in comments/docstrings across backend,
  network, manifest, supervise, git_gate, yaml_subset, agent_provider,
  sidecar_bundle, sidecar_init, egress_addon_core modules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:54:06 +00:00

125 lines
4.1 KiB
Python

"""Docker network plumbing for the per-agent egress topology.
The agent container sits on a Docker `--internal` network (no default
gateway). Egress straddles that network and a per-agent user-defined
bridge for upstream traffic. We deliberately do NOT use Docker's legacy
`bridge` network because only user-defined bridges run Docker's
embedded DNS resolver, which egress needs to resolve upstream hostnames.
Naming: bot-bottle-net-<slug> (internal),
bot-bottle-egress-<slug> (egress). Numeric suffix on conflict
(-2, -3, ..., capped at 100).
"""
from __future__ import annotations
import subprocess
from ...log import die, info, warn
def network_name_for_slug(slug: str) -> str:
return f"bot-bottle-net-{slug}"
def network_egress_name_for_slug(slug: str) -> str:
return f"bot-bottle-egress-{slug}"
def network_exists(name: str) -> bool:
"""Uses `docker network inspect`, not `docker network ls -f name=...`,
because the latter does substring matching."""
return (
subprocess.run(
["docker", "network", "inspect", name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
).returncode
== 0
)
def _network_create_with_prefix(base: str, internal: bool) -> str:
"""Create a per-agent Docker network whose name is <base> (with
-2, -3, ... appended on conflict, capped at 100). Returns the
resolved name."""
name = base
suffix = 2
while network_exists(name):
name = f"{base}-{suffix}"
suffix += 1
if suffix > 100:
die(
f"could not find a free network name after {base}-99; "
f"clean up old networks with 'docker network rm <name>'"
)
kind = "internal" if internal else "bridge (egress)"
args = ["docker", "network", "create"]
if internal:
args.append("--internal")
args.append(name)
info(f"creating {kind} network {name}")
result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, check=False)
if result.returncode != 0:
flag = " --internal" if internal else ""
die(f"docker network create{flag} {name} failed")
return name
def network_create_internal(slug: str) -> str:
"""Create a Docker `--internal` network for the agent. Returns the
resolved name."""
return _network_create_with_prefix(network_name_for_slug(slug), internal=True)
def network_create_egress(slug: str) -> str:
"""Create a per-agent user-defined bridge (NOT the legacy `bridge`)
so the egress sidecar has working DNS for upstream hostnames."""
return _network_create_with_prefix(network_egress_name_for_slug(slug), internal=False)
def network_inspect_cidr(name: str) -> str:
"""Return the IPv4 CIDR Docker assigned to a user-defined network."""
result = subprocess.run(
["docker", "network", "inspect",
"--format", "{{range .IPAM.Config}}{{.Subnet}}{{end}}", name],
capture_output=True, text=True, check=False,
)
if result.returncode != 0:
die(f"docker network inspect {name} failed: {result.stderr.strip()}")
cidr = result.stdout.strip()
if not cidr:
die(f"network {name!r} has no IPAM subnet configured")
return cidr
def network_attach(network: str, container: str) -> None:
result = subprocess.run(
["docker", "network", "connect", network, container],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
if result.returncode != 0:
die(f"docker network connect {network} {container} failed")
def network_remove(name: str) -> bool:
"""Idempotent: a missing network is treated as success so this can
be called from a teardown trap. Returns True if removal succeeded
(or the network was already gone)."""
if not network_exists(name):
return True
result = subprocess.run(
["docker", "network", "rm", name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
if result.returncode != 0:
warn(f"failed to remove network {name}; clean up with 'docker network rm {name}'")
return False
return True