refactor(gateway): move the data-plane daemons into a bot_bottle.gateway package
test / integration-docker (pull_request) Successful in 11s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m19s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 7s
test / integration-docker (pull_request) Successful in 11s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m19s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 7s
Separate the gateway (data plane) from the orchestrator (control plane) at the
module level. The gateway runtime files move out of the package root — and the
backend-neutral Gateway lifecycle ABC + GATEWAY_* constants move out of
orchestrator/ — into a new bot_bottle/gateway/ package:
gateway/__init__.py (was orchestrator/gateway.py: Gateway ABC + consts
+ rotate_gateway_ca)
gateway/gateway_init.py (the PID-1 daemon supervisor)
gateway/egress_addon.py, egress_addon_core.py, egress_dlp_config.py,
dlp_detectors.py (the egress mitmproxy daemon)
gateway/git_http_backend.py (the git-http daemon)
gateway/git_gate_render.py (the git-gate pre-receive rendering)
gateway/supervise_server.py (the supervise MCP daemon)
gateway/policy_resolver.py (the data-plane control-plane RPC client)
orchestrator/ now holds only control-plane files. The shared plan/types/auth
layer (egress.py=EgressPlan, git_gate.py=GitGatePlan, supervise.py,
supervise_types.py, control_auth.py) and the launch-time git-gate provisioning
helpers stay at root, so orchestrator/ and backend/ still own them.
Because these daemons are invoked as `python3 -m bot_bottle.<name>`, loaded flat
by mitmproxy, and referenced in Dockerfile.gateway, the move updates more than
Python imports: the `-m` invocations (firecracker/macOS infra scripts), the
Dockerfile.gateway addon shim + ENTRYPOINT, gateway_init's _DAEMONS module
paths, and the git-gate CGI heredocs all now point at bot_bottle.gateway.*.
No behavior change; full unit suite green (2251).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ from ...docker_cmd import run_docker
|
||||
from ...egress import EgressPlan
|
||||
from ...git_gate import GitGatePlan
|
||||
from ...orchestrator.client import OrchestratorClient
|
||||
from ...orchestrator.gateway import GATEWAY_NETWORK
|
||||
from ...gateway import GATEWAY_NETWORK
|
||||
from ...orchestrator.lifecycle import INFRA_NAME, OrchestratorService
|
||||
from ...orchestrator.secret_store import ENV_VAR_SECRET_NAME
|
||||
from ...orchestrator.reprovision import reprovision_bottles
|
||||
|
||||
@@ -11,7 +11,7 @@ from ...paths import (
|
||||
host_control_plane_token,
|
||||
host_gateway_ca_dir,
|
||||
)
|
||||
from ...orchestrator.gateway import (
|
||||
from ...gateway import (
|
||||
Gateway, GATEWAY_IMAGE, GATEWAY_NAME, GATEWAY_NETWORK, GATEWAY_DOCKERFILE,
|
||||
REPO_ROOT, GATEWAY_LABEL, MITMPROXY_HOME, DEFAULT_CA_TIMEOUT_SECONDS,
|
||||
CA_POLL_SECONDS, GATEWAY_CA_CERT, GatewayError
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
|
||||
from ..bottle_state import egress_state_dir
|
||||
from ..egress import EGRESS_ROUTES_FILENAME
|
||||
from ..egress_addon_core import LOG_OFF, load_config
|
||||
from ..gateway.egress_addon_core import LOG_OFF, load_config
|
||||
|
||||
|
||||
class EgressApplyError(RuntimeError):
|
||||
|
||||
@@ -542,7 +542,7 @@ BOT_BOTTLE_ROOT=/var/lib/bot-bottle BOT_BOTTLE_CONTROL_PLANE_TOKEN="$CP_KEY" pyt
|
||||
BOT_BOTTLE_GATEWAY_DAEMONS=egress,git-http,supervise \\
|
||||
BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{CONTROL_PLANE_PORT} \\
|
||||
BOT_BOTTLE_CONTROL_AUTH_JWT="$GW_JWT" \\
|
||||
python3 -m bot_bottle.gateway_init &
|
||||
python3 -m bot_bottle.gateway.gateway_init &
|
||||
|
||||
# Reap as PID 1; children are backgrounded, so `wait` blocks.
|
||||
while : ; do wait ; done
|
||||
|
||||
@@ -12,7 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from ...orchestrator.gateway import GatewayError
|
||||
from ...gateway import GatewayError
|
||||
from . import util as container_mod
|
||||
|
||||
# The shared host-only network the infra container and every agent bottle sit
|
||||
|
||||
@@ -41,7 +41,7 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from ... import log
|
||||
from ...orchestrator.gateway import GATEWAY_CA_CERT, MITMPROXY_HOME
|
||||
from ...gateway import GATEWAY_CA_CERT, MITMPROXY_HOME
|
||||
from ...orchestrator.lifecycle import (
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
||||
@@ -107,7 +107,7 @@ def _init_script(port: int) -> str:
|
||||
# control-plane RPC and never opens bot-bottle.db (PRD 0070 / #469).
|
||||
f"( cd /app && BOT_BOTTLE_GATEWAY_DAEMONS={_GATEWAY_DAEMONS} "
|
||||
f"BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{port} "
|
||||
f"python3 -m bot_bottle.gateway_init ) &\n"
|
||||
f"python3 -m bot_bottle.gateway.gateway_init ) &\n"
|
||||
"while : ; do wait ; done\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ from ...git_gate import (
|
||||
provision_git_gate_dynamic_keys,
|
||||
revoke_git_gate_provisioned_keys,
|
||||
)
|
||||
from ...git_http_backend import DEFAULT_PORT as _GIT_HTTP_PORT
|
||||
from ...gateway.git_http_backend import DEFAULT_PORT as _GIT_HTTP_PORT
|
||||
from ...image_cache import check_stale
|
||||
from ...log import die, info, warn
|
||||
from .. import BottleImages
|
||||
|
||||
@@ -16,7 +16,7 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .egress_addon_core import (
|
||||
from .gateway.egress_addon_core import (
|
||||
ON_MATCH_REDACT,
|
||||
HeaderMatch as CoreHeaderMatch,
|
||||
MatchEntry as CoreMatchEntry,
|
||||
|
||||
@@ -16,8 +16,8 @@ import typing
|
||||
from mitmproxy import http # type: ignore[import-not-found] # pylint: disable=import-error
|
||||
|
||||
from bot_bottle.constants import IDENTITY_HEADER
|
||||
from bot_bottle.dlp_detectors import redact_tokens, strip_crlf
|
||||
from bot_bottle.egress_addon_core import (
|
||||
from bot_bottle.gateway.dlp_detectors import redact_tokens, strip_crlf
|
||||
from bot_bottle.gateway.egress_addon_core import (
|
||||
LOG_BLOCKS,
|
||||
LOG_FULL,
|
||||
DEFAULT_OUTBOUND_ON_MATCH,
|
||||
@@ -40,7 +40,7 @@ from bot_bottle.egress_addon_core import (
|
||||
scan_inbound,
|
||||
scan_outbound,
|
||||
)
|
||||
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
from bot_bottle.supervise_types import (
|
||||
STATUS_APPROVED,
|
||||
STATUS_MODIFIED,
|
||||
@@ -16,7 +16,7 @@ import re
|
||||
import typing
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .yaml_subset import YamlSubsetError, parse_yaml_subset
|
||||
from ..yaml_subset import YamlSubsetError, parse_yaml_subset
|
||||
|
||||
# DLP detector-config parsing lives in a sibling module. Re-exported below
|
||||
# so existing `from egress_addon_core import ON_MATCH_*` callers keep working.
|
||||
@@ -100,8 +100,8 @@ _DAEMONS: tuple[_DaemonSpec, ...] = (
|
||||
)),
|
||||
_DaemonSpec("egress", ("/bin/sh", "/app/egress-entrypoint.sh")),
|
||||
_DaemonSpec("git-gate", ("/bin/sh", "/git-gate-entrypoint.sh")),
|
||||
_DaemonSpec("git-http", ("python3", "-m", "bot_bottle.git_http_backend")),
|
||||
_DaemonSpec("supervise", ("python3", "-m", "bot_bottle.supervise_server")),
|
||||
_DaemonSpec("git-http", ("python3", "-m", "bot_bottle.gateway.git_http_backend")),
|
||||
_DaemonSpec("supervise", ("python3", "-m", "bot_bottle.gateway.supervise_server")),
|
||||
)
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import shlex
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||
from .manifest import ManifestBottle, ManifestGitEntry
|
||||
from ..constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||
from ..manifest import ManifestBottle, ManifestGitEntry
|
||||
|
||||
# Short network alias for git-gate inside the gateway. The
|
||||
# agent's `.gitconfig` insteadOf rewrites resolve through this name.
|
||||
@@ -282,7 +282,7 @@ from pathlib import Path
|
||||
# identity_token), resolved server-side, so the proposal lands under the
|
||||
# calling bottle exactly as a direct write once did.
|
||||
try:
|
||||
from bot_bottle.policy_resolver import PolicyResolver, PolicyResolveError
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolver, PolicyResolveError
|
||||
from bot_bottle.supervise_types import TOOL_GITLEAKS_ALLOW
|
||||
except ImportError:
|
||||
from policy_resolver import PolicyResolver, PolicyResolveError
|
||||
@@ -374,7 +374,7 @@ import sys
|
||||
# Non-blocking poll over the control plane. A decided proposal is archived
|
||||
# server-side on read, so no separate archive step is needed here.
|
||||
try:
|
||||
from bot_bottle.policy_resolver import PolicyResolver, PolicyResolveError
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolver, PolicyResolveError
|
||||
except ImportError:
|
||||
from policy_resolver import PolicyResolver, PolicyResolveError
|
||||
|
||||
@@ -27,7 +27,7 @@ from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from bot_bottle.constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
|
||||
|
||||
DEFAULT_PORT = 9420
|
||||
@@ -58,10 +58,10 @@ import typing
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bot_bottle.constants import IDENTITY_HEADER
|
||||
from bot_bottle.egress_addon_core import (
|
||||
from bot_bottle.gateway.egress_addon_core import (
|
||||
LOG_OFF, load_config, resolve_client_context, route_to_yaml_dict,
|
||||
)
|
||||
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError, PolicyResolver
|
||||
from bot_bottle import supervise as _sv
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ from .manifest import ManifestBottle
|
||||
# Rendering and the deploy-key lifecycle live in sibling modules; the
|
||||
# names are re-exported here (see __all__) so existing
|
||||
# `from bot_bottle.git_gate import …` callers are unchanged.
|
||||
from .git_gate_render import (
|
||||
from .gateway.git_gate_render import (
|
||||
GIT_GATE_HOSTNAME,
|
||||
GIT_GATE_TIMEOUT_SECS,
|
||||
GitGateUpstream,
|
||||
|
||||
@@ -17,7 +17,7 @@ from .bottle_state import globalize_slug
|
||||
from .errors import MissingEnvVarError
|
||||
from .log import info
|
||||
from .manifest import ManifestBottle, ManifestGitEntry
|
||||
from .git_gate_render import GitGateUpstream
|
||||
from .gateway.git_gate_render import GitGateUpstream
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .git_gate import GitGatePlan
|
||||
|
||||
@@ -38,7 +38,7 @@ from .broker import (
|
||||
verify_request,
|
||||
)
|
||||
from .docker_broker import DockerBroker, DockerBrokerError
|
||||
from .gateway import Gateway, GatewayError
|
||||
from ..gateway import Gateway, GatewayError
|
||||
from .service import Orchestrator
|
||||
from .control_plane import ControlPlaneServer, dispatch, make_server
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from ..paths import (
|
||||
host_control_plane_token,
|
||||
host_gateway_ca_dir,
|
||||
)
|
||||
from .gateway import (
|
||||
from ..gateway import (
|
||||
GATEWAY_DOCKERFILE,
|
||||
GATEWAY_IMAGE,
|
||||
GATEWAY_NETWORK,
|
||||
|
||||
@@ -25,7 +25,7 @@ from pathlib import Path
|
||||
|
||||
from ..docker_cmd import run_docker
|
||||
from ..paths import host_gateway_ca_dir
|
||||
from .gateway import GATEWAY_NAME, rotate_gateway_ca
|
||||
from ..gateway import GATEWAY_NAME, rotate_gateway_ca
|
||||
from .lifecycle import INFRA_NAME
|
||||
|
||||
# The containers whose mitmproxy would still be serving the old CA from memory:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Per-bottle supervise plane (PRD 0013).
|
||||
|
||||
The supervise plane is the per-bottle MCP daemon plus its host-side
|
||||
queue/audit support. The daemon (bot_bottle.supervise_server)
|
||||
queue/audit support. The daemon (bot_bottle.gateway.supervise_server)
|
||||
sits on the bottle's internal network and exposes MCP tools the agent
|
||||
calls when it needs an operator-reviewed egress change:
|
||||
|
||||
@@ -18,7 +18,7 @@ the response and returns `{status, notes}` to the agent.
|
||||
This module defines the host-side library: dataclasses for the queue
|
||||
record shapes, queue read/write helpers, the audit log writer, and the
|
||||
diff renderer. The in-gateway daemon lives in
|
||||
bot_bottle/supervise_server.py; the supervise daemon's container
|
||||
bot_bottle/gateway/supervise_server.py; the supervise daemon's container
|
||||
lifecycle is owned by the gateway (PRD 0024).
|
||||
|
||||
For 0013 the supervisor's approval handlers are deliberately no-ops:
|
||||
|
||||
Reference in New Issue
Block a user