refactor: repo reorganization for clearer separation of concerns #477
+2
-2
@@ -102,7 +102,7 @@ RUN pip install --no-cache-dir /src/
|
||||
# WORKDIR here also creates /app so the shim + COPYs below can write into it
|
||||
# (nothing created /app before this point).
|
||||
WORKDIR /app
|
||||
RUN printf 'from bot_bottle.egress_addon import addons\n' > /app/egress_addon.py
|
||||
RUN printf 'from bot_bottle.gateway.egress_addon import addons\n' > /app/egress_addon.py
|
||||
COPY bot_bottle/egress_entrypoint.sh /app/egress-entrypoint.sh
|
||||
RUN chmod +x /app/egress-entrypoint.sh
|
||||
|
||||
@@ -123,4 +123,4 @@ EXPOSE 8888 9099 9418 9420 9100
|
||||
|
||||
# PID 1 is the supervisor. It owns signal handling and exit-code
|
||||
# propagation; no `exec` chain in the entrypoint itself.
|
||||
ENTRYPOINT ["python3", "-m", "bot_bottle.gateway_init"]
|
||||
ENTRYPOINT ["python3", "-m", "bot_bottle.gateway.gateway_init"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -91,7 +91,7 @@ class TestGatewayImage(unittest.TestCase):
|
||||
# Probe that the package imports resolve inside the image.
|
||||
rc, out = self._run_in_image(
|
||||
"python3", "-c",
|
||||
"from bot_bottle import supervise, supervise_server; print('ok')",
|
||||
"from bot_bottle import supervise; from bot_bottle.gateway import supervise_server; print('ok')",
|
||||
)
|
||||
self.assertEqual(0, rc, msg=out)
|
||||
self.assertIn("ok", out)
|
||||
|
||||
@@ -29,7 +29,7 @@ from bot_bottle.backend.docker.consolidated_launch import (
|
||||
from bot_bottle.backend.docker.egress import EGRESS_PORT
|
||||
from bot_bottle.backend.docker.gateway_net import next_free_ip
|
||||
from bot_bottle.orchestrator.client import OrchestratorClient
|
||||
from bot_bottle.orchestrator.gateway import GATEWAY_IMAGE, GATEWAY_NAME, GATEWAY_NETWORK
|
||||
from bot_bottle.gateway import GATEWAY_IMAGE, GATEWAY_NAME, GATEWAY_NETWORK
|
||||
from bot_bottle.orchestrator.lifecycle import OrchestratorService
|
||||
from tests._docker import skip_unless_docker
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import base64
|
||||
import gzip
|
||||
import unittest
|
||||
|
||||
from bot_bottle.dlp_detectors import (
|
||||
from bot_bottle.gateway.dlp_detectors import (
|
||||
ENTROPY_BLOCK_THRESHOLD,
|
||||
PARTIAL_MATCH_MIN_LEN,
|
||||
REDACT,
|
||||
@@ -465,17 +465,17 @@ class TestMatchedAndSafeTokens(unittest.TestCase):
|
||||
|
||||
class TestStripCrlf(unittest.TestCase):
|
||||
def test_removes_url_encoded_crlf(self):
|
||||
from bot_bottle.dlp_detectors import strip_crlf
|
||||
from bot_bottle.gateway.dlp_detectors import strip_crlf
|
||||
out = strip_crlf("next=%0d%0aX-Injected: evil")
|
||||
self.assertNotRegex(out, r"%0[dD]%0[aA]")
|
||||
|
||||
def test_removes_literal_header_injection(self):
|
||||
from bot_bottle.dlp_detectors import strip_crlf
|
||||
from bot_bottle.gateway.dlp_detectors import strip_crlf
|
||||
out = strip_crlf("value\r\nX-Injected: evil")
|
||||
self.assertIsNone(scan_crlf_injection(out))
|
||||
|
||||
def test_leaves_clean_text_unchanged(self):
|
||||
from bot_bottle.dlp_detectors import strip_crlf
|
||||
from bot_bottle.gateway.dlp_detectors import strip_crlf
|
||||
self.assertEqual("/api/v1/data?q=hello", strip_crlf("/api/v1/data?q=hello"))
|
||||
|
||||
class TestAlnumProjection(unittest.TestCase):
|
||||
|
||||
+11
-11
@@ -344,7 +344,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
self.assertEqual([], parse_yaml_subset(rendered)["routes"])
|
||||
|
||||
def test_round_trip_through_addon_core(self):
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
b = _bottle([
|
||||
{"host": "api.github.com",
|
||||
"auth": {"scheme": "Bearer", "token_ref": "GH_PAT"},
|
||||
@@ -363,7 +363,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
self.assertEqual("", addon_routes[2].auth_scheme)
|
||||
|
||||
def test_dlp_round_trips(self):
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
b = _bottle([{"host": "x.example", "dlp": {
|
||||
"outbound_detectors": ["token_patterns"],
|
||||
"inbound_detectors": False,
|
||||
@@ -375,7 +375,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
self.assertEqual((), addon_routes[0].inbound_detectors)
|
||||
|
||||
def test_outbound_on_match_round_trips(self):
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
b = _bottle([{"host": "logs.example", "dlp": {
|
||||
"outbound_on_match": "redact",
|
||||
}}])
|
||||
@@ -392,7 +392,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
self.assertNotIn("outbound_on_match", rendered)
|
||||
|
||||
def test_git_fetch_policy_round_trips(self):
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
b = _bottle([{"host": "github.com", "git": {"fetch": True}}])
|
||||
routes = egress_routes_for_bottle(b)
|
||||
rendered = egress_render_routes(routes)
|
||||
@@ -405,7 +405,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
it, but the renderer in between dropped it — so the flag never reached
|
||||
the proxy and registry pulls kept failing with "unauthorized" while
|
||||
the config looked correct everywhere it was inspected."""
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
b = _bottle([{"host": "registry-1.docker.io", "preserve_auth": True}])
|
||||
routes = egress_routes_for_bottle(b)
|
||||
rendered = egress_render_routes(routes)
|
||||
@@ -416,7 +416,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
b = _bottle([{"host": "x.example"}])
|
||||
rendered = egress_render_routes(egress_routes_for_bottle(b))
|
||||
self.assertNotIn("preserve_auth", rendered)
|
||||
from bot_bottle.egress_addon_core import load_config
|
||||
from bot_bottle.gateway.egress_addon_core import load_config
|
||||
self.assertFalse(load_config(rendered).routes[0].preserve_auth)
|
||||
|
||||
def test_log_zero_omitted_from_render(self):
|
||||
@@ -434,7 +434,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
self.assertTrue(rendered.startswith(f"log: {level}\n"))
|
||||
|
||||
def test_log_level_round_trips_to_addon_core(self):
|
||||
from bot_bottle.egress_addon_core import load_config, LOG_FULL
|
||||
from bot_bottle.gateway.egress_addon_core import load_config, LOG_FULL
|
||||
b = _bottle([{"host": "x.example"}])
|
||||
routes = egress_routes_for_bottle(b)
|
||||
rendered = egress_render_routes(routes, log=LOG_FULL)
|
||||
@@ -444,7 +444,7 @@ class TestRenderRoutes(unittest.TestCase):
|
||||
|
||||
def test_log_via_manifest_flows_to_render(self):
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
from bot_bottle.egress_addon_core import load_config, LOG_BLOCKS
|
||||
from bot_bottle.gateway.egress_addon_core import load_config, LOG_BLOCKS
|
||||
m = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": {"egress": {
|
||||
"log": 1,
|
||||
@@ -512,7 +512,7 @@ class TestRenderRoutesEscaping(unittest.TestCase):
|
||||
self.assertEqual('Bear"er', parsed[0]["inspect"]["auth_scheme"])
|
||||
|
||||
def test_path_value_with_double_quote_round_trips(self):
|
||||
from bot_bottle.egress_addon_core import PathMatch, MatchEntry
|
||||
from bot_bottle.gateway.egress_addon_core import PathMatch, MatchEntry
|
||||
routes = (EgressRoute(
|
||||
host="api.example",
|
||||
matches=(MatchEntry(paths=(PathMatch(type="prefix", value='/v1/"quoted"/'),)),),
|
||||
@@ -521,7 +521,7 @@ class TestRenderRoutesEscaping(unittest.TestCase):
|
||||
self.assertEqual('/v1/"quoted"/', parsed[0]["inspect"]["matches"][0]["paths"][0]["value"])
|
||||
|
||||
def test_header_value_with_double_quote_round_trips(self):
|
||||
from bot_bottle.egress_addon_core import HeaderMatch, MatchEntry
|
||||
from bot_bottle.gateway.egress_addon_core import HeaderMatch, MatchEntry
|
||||
routes = (EgressRoute(
|
||||
host="api.example",
|
||||
matches=(MatchEntry(headers=(HeaderMatch(name="x-h", value='val"ue'),)),),
|
||||
@@ -598,7 +598,7 @@ class TestCanaryGeneration(unittest.TestCase):
|
||||
self.assertNotEqual(plan_a.canary, plan_b.canary)
|
||||
|
||||
def test_canary_detected_by_scan_known_secrets(self):
|
||||
from bot_bottle.dlp_detectors import scan_known_secrets
|
||||
from bot_bottle.gateway.dlp_detectors import scan_known_secrets
|
||||
|
||||
plan = self._make_plan()
|
||||
env = {plan.canary_env: plan.canary}
|
||||
|
||||
@@ -12,7 +12,7 @@ import unittest
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from bot_bottle.egress_addon_core import (
|
||||
from bot_bottle.gateway.egress_addon_core import (
|
||||
LOG_BLOCKS,
|
||||
LOG_FULL,
|
||||
LOG_OFF,
|
||||
@@ -1377,15 +1377,15 @@ class TestScanOutboundEnhanced(unittest.TestCase):
|
||||
|
||||
class TestOutboundDetectorNames(unittest.TestCase):
|
||||
def test_entropy_in_outbound_detector_names(self):
|
||||
from bot_bottle.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
from bot_bottle.gateway.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
self.assertIn("entropy", OUTBOUND_DETECTOR_NAMES)
|
||||
|
||||
def test_known_secrets_in_outbound_detector_names(self):
|
||||
from bot_bottle.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
from bot_bottle.gateway.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
self.assertIn("known_secrets", OUTBOUND_DETECTOR_NAMES)
|
||||
|
||||
def test_token_patterns_in_outbound_detector_names(self):
|
||||
from bot_bottle.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
from bot_bottle.gateway.egress_addon_core import OUTBOUND_DETECTOR_NAMES
|
||||
self.assertIn("token_patterns", OUTBOUND_DETECTOR_NAMES)
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ def _ensure_shims() -> None:
|
||||
|
||||
_ensure_shims()
|
||||
|
||||
from bot_bottle.egress_addon import EgressAddon # noqa: E402 (import after shims)
|
||||
from bot_bottle.gateway.egress_addon import EgressAddon # noqa: E402 (import after shims)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -194,22 +194,22 @@ def _ensure_shims() -> None:
|
||||
|
||||
_ensure_shims()
|
||||
|
||||
import bot_bottle.egress_addon as _ea_mod # noqa: E402 (after shims)
|
||||
from bot_bottle.egress_addon import EgressAddon # noqa: E402 (after shims)
|
||||
from bot_bottle.egress_addon import ( # noqa: E402
|
||||
import bot_bottle.gateway.egress_addon as _ea_mod # noqa: E402 (after shims)
|
||||
from bot_bottle.gateway.egress_addon import EgressAddon # noqa: E402 (after shims)
|
||||
from bot_bottle.gateway.egress_addon import ( # noqa: E402
|
||||
DEFAULT_INBOUND_SCAN_LIMIT_BYTES,
|
||||
DEFAULT_TOKEN_ALLOW_TIMEOUT_SECONDS,
|
||||
_inbound_scan_limit_from_env,
|
||||
_token_allow_timeout_from_env,
|
||||
)
|
||||
from bot_bottle.egress_addon_core import ( # noqa: E402
|
||||
from bot_bottle.gateway.egress_addon_core import ( # noqa: E402
|
||||
Config,
|
||||
LOG_BLOCKS,
|
||||
LOG_FULL,
|
||||
Route,
|
||||
route_to_yaml_dict,
|
||||
)
|
||||
from bot_bottle.policy_resolver import PolicyResolveError # noqa: E402
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -8,7 +8,7 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from bot_bottle.egress_addon_core import (
|
||||
from bot_bottle.gateway.egress_addon_core import (
|
||||
HeaderMatch,
|
||||
MatchEntry,
|
||||
PathMatch,
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from bot_bottle.egress_addon_core import (
|
||||
from bot_bottle.gateway.egress_addon_core import (
|
||||
DENY_RESOLVER_ERROR,
|
||||
DENY_UNATTRIBUTED,
|
||||
DENY_UNPARSEABLE,
|
||||
@@ -12,7 +12,7 @@ from bot_bottle.egress_addon_core import (
|
||||
resolve_client_config,
|
||||
resolve_client_context,
|
||||
)
|
||||
from bot_bottle.policy_resolver import PolicyResolveError
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError
|
||||
|
||||
|
||||
class _FakeResolver:
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestBuildInfraRootfs(unittest.TestCase):
|
||||
self.assertIn("bot_bottle.orchestrator", init)
|
||||
# Gateway launches via the installed package (there is no
|
||||
# /app/gateway_init.py file since the daemons moved into bot_bottle).
|
||||
self.assertIn("bot_bottle.gateway_init", init)
|
||||
self.assertIn("bot_bottle.gateway.gateway_init", init)
|
||||
self.assertIn("export PATH=", init)
|
||||
# Persistent registry volume mounted at the DB dir before the CP starts.
|
||||
self.assertIn("/dev/vdb", init)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Unit: gateway data-plane init supervisor (PRD 0070; PRD 0024 bundle shape).
|
||||
|
||||
Tests both the helper functions in `bot_bottle.gateway_init`
|
||||
Tests both the helper functions in `bot_bottle.gateway.gateway_init`
|
||||
and the supervisor's end-to-end signal / exit-code behavior. The
|
||||
end-to-end tests use real subprocesses (`sleep`, `/bin/sh -c '...'`) —
|
||||
short-lived, no docker required — so they run under `tests/unit/`
|
||||
@@ -18,7 +18,7 @@ import warnings
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle.gateway_init import (
|
||||
from bot_bottle.gateway.gateway_init import (
|
||||
_DaemonSpec,
|
||||
_Supervisor,
|
||||
_argv_for_daemon,
|
||||
@@ -489,7 +489,7 @@ class TestSupervisor(unittest.TestCase):
|
||||
time.sleep(0.3) # let `trap` register
|
||||
sup.request_shutdown(reason="test")
|
||||
|
||||
with patch("bot_bottle.gateway_init._GRACE_SECONDS", 0.3):
|
||||
with patch("bot_bottle.gateway.gateway_init._GRACE_SECONDS", 0.3):
|
||||
rc = self._drive(sup, max_wait_s=4.0)
|
||||
|
||||
# Process was SIGKILL'd → returncode -9 on POSIX.
|
||||
@@ -531,7 +531,7 @@ class TestMainEndToEnd(unittest.TestCase):
|
||||
|
||||
helper = (
|
||||
"import os, runpy, sys\n"
|
||||
"from bot_bottle import gateway_init as si\n"
|
||||
"from bot_bottle.gateway import gateway_init as si\n"
|
||||
"si._DAEMONS = (\n"
|
||||
f" si._DaemonSpec('alpha', ({SLEEP!r},'30')),\n"
|
||||
f" si._DaemonSpec('beta', ({SLEEP!r},'30')),\n"
|
||||
|
||||
@@ -230,7 +230,7 @@ class TestHookRender(unittest.TestCase):
|
||||
# execute from the bare repo directory, so the embedded Python must
|
||||
# include /app and support both import layouts.
|
||||
self.assertIn('PYTHONPATH="/app${PYTHONPATH:+:$PYTHONPATH}"', hook)
|
||||
self.assertIn("from bot_bottle.policy_resolver import PolicyResolver", hook)
|
||||
self.assertIn("from bot_bottle.gateway.policy_resolver import PolicyResolver", hook)
|
||||
self.assertIn("from policy_resolver import PolicyResolver", hook)
|
||||
|
||||
def test_inline_gitleaks_allow_fails_closed_without_supervisor(self):
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from bot_bottle.git_gate_render import (
|
||||
from bot_bottle.gateway.git_gate_render import (
|
||||
GitGateUpstream,
|
||||
git_gate_render_entrypoint,
|
||||
git_gate_render_provision,
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from bot_bottle.git_gate import GIT_GATE_TIMEOUT_SECS
|
||||
from bot_bottle.git_http_backend import GitHttpHandler, MAX_BODY_BYTES
|
||||
from bot_bottle.gateway.git_http_backend import GitHttpHandler, MAX_BODY_BYTES
|
||||
|
||||
|
||||
# The git-http backend is resolver-only: every request is attributed to a
|
||||
@@ -199,7 +199,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
||||
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
|
||||
]
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
side_effect=calls,
|
||||
) as run:
|
||||
request = urllib.request.Request(
|
||||
@@ -265,7 +265,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
||||
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
|
||||
]
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
side_effect=calls,
|
||||
) as run:
|
||||
req = urllib.request.Request(
|
||||
@@ -309,7 +309,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
||||
|
||||
denial = b"git-gate: upstream fetch failed; refusing to serve stale data\n"
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
["hook"], 1, b"", denial,
|
||||
),
|
||||
@@ -355,7 +355,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
||||
self.addCleanup(server.server_close)
|
||||
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
["hook"], 2, b"", b"",
|
||||
),
|
||||
@@ -402,7 +402,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
||||
self.addCleanup(server.server_close)
|
||||
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
side_effect=PermissionError(13, "Permission denied"),
|
||||
):
|
||||
buf = io.StringIO()
|
||||
@@ -461,7 +461,7 @@ class TestMalformedStatusHeader(unittest.TestCase):
|
||||
|
||||
def _get_with_backend_response(self, cgi_response: bytes) -> int:
|
||||
with mock.patch(
|
||||
"bot_bottle.git_http_backend.subprocess.run",
|
||||
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||
return_value=mock.Mock(returncode=0, stdout=cgi_response),
|
||||
):
|
||||
req = urllib.request.Request(
|
||||
@@ -545,7 +545,7 @@ class TestContentLengthBounds(unittest.TestCase):
|
||||
# With a valid Content-Length the handler proceeds into
|
||||
# git http-backend; that will fail (no real git repo) but the
|
||||
# status won't be 400 or 413.
|
||||
with mock.patch("bot_bottle.git_http_backend.subprocess.run") as run:
|
||||
with mock.patch("bot_bottle.gateway.git_http_backend.subprocess.run") as run:
|
||||
run.return_value = mock.Mock(
|
||||
returncode=0,
|
||||
stdout=(
|
||||
|
||||
@@ -10,8 +10,8 @@ from __future__ import annotations
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle.git_http_backend import resolve_sandbox_root
|
||||
from bot_bottle.policy_resolver import PolicyResolveError
|
||||
from bot_bottle.gateway.git_http_backend import resolve_sandbox_root
|
||||
from bot_bottle.gateway.policy_resolver import PolicyResolveError
|
||||
|
||||
_BASE = Path("/git")
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestInfraRun(unittest.TestCase):
|
||||
self.assertIn("bot_bottle.orchestrator", script)
|
||||
# Gateway launches via the installed package (there is no
|
||||
# /app/gateway_init.py file since the daemons moved into bot_bottle).
|
||||
self.assertIn("bot_bottle.gateway_init", script)
|
||||
self.assertIn("bot_bottle.gateway.gateway_init", script)
|
||||
self.assertIn("127.0.0.1", script) # they reach each other on loopback
|
||||
|
||||
def test_db_is_a_container_only_volume(self) -> None:
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bot_bottle.backend.docker.gateway import DockerGateway
|
||||
from bot_bottle.orchestrator.gateway import (
|
||||
from bot_bottle.gateway import (
|
||||
GATEWAY_CA_CERT,
|
||||
GATEWAY_NAME,
|
||||
GatewayError,
|
||||
|
||||
@@ -8,7 +8,7 @@ import urllib.error
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from bot_bottle.orchestrator.gateway import GatewayError
|
||||
from bot_bottle.gateway import GatewayError
|
||||
from bot_bottle.orchestrator.lifecycle import (
|
||||
INFRA_NAME,
|
||||
INFRA_SOURCE_HASH_LABEL,
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle.egress import EgressPlan, EgressRoute
|
||||
from bot_bottle.egress_addon_core import LOG_BLOCKS, load_config
|
||||
from bot_bottle.gateway.egress_addon_core import LOG_BLOCKS, load_config
|
||||
from bot_bottle.orchestrator.registration import (
|
||||
RegistrationInputs,
|
||||
egress_policy,
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bot_bottle.orchestrator import rotate_ca
|
||||
from bot_bottle.orchestrator.gateway import GATEWAY_NAME
|
||||
from bot_bottle.gateway import GATEWAY_NAME
|
||||
from bot_bottle.orchestrator.lifecycle import INFRA_NAME
|
||||
from bot_bottle.paths import host_gateway_ca_dir
|
||||
from tests.unit import use_bottle_root
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
import urllib.error
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from bot_bottle.policy_resolver import (
|
||||
from bot_bottle.gateway.policy_resolver import (
|
||||
CONTROL_AUTH_HEADER,
|
||||
CONTROL_AUTH_JWT_ENV,
|
||||
PolicyResolveError,
|
||||
@@ -15,7 +15,7 @@ from bot_bottle.policy_resolver import (
|
||||
_control_auth_headers,
|
||||
)
|
||||
|
||||
_URLOPEN = "bot_bottle.policy_resolver.urllib.request.urlopen"
|
||||
_URLOPEN = "bot_bottle.gateway.policy_resolver.urllib.request.urlopen"
|
||||
|
||||
|
||||
def _resp(payload: object) -> MagicMock:
|
||||
|
||||
@@ -22,8 +22,8 @@ from bot_bottle import supervise as _sv
|
||||
from bot_bottle.store import queue_store as _qs
|
||||
from bot_bottle.store import audit_store as _as
|
||||
|
||||
from bot_bottle import supervise_server # noqa: E402
|
||||
from bot_bottle.supervise_server import (
|
||||
from bot_bottle.gateway import supervise_server # noqa: E402
|
||||
from bot_bottle.gateway.supervise_server import (
|
||||
ERR_INTERNAL,
|
||||
ERR_INVALID_PARAMS,
|
||||
ERR_INVALID_REQUEST,
|
||||
|
||||
Reference in New Issue
Block a user