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:
+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
|
# WORKDIR here also creates /app so the shim + COPYs below can write into it
|
||||||
# (nothing created /app before this point).
|
# (nothing created /app before this point).
|
||||||
WORKDIR /app
|
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
|
COPY bot_bottle/egress_entrypoint.sh /app/egress-entrypoint.sh
|
||||||
RUN chmod +x /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
|
# PID 1 is the supervisor. It owns signal handling and exit-code
|
||||||
# propagation; no `exec` chain in the entrypoint itself.
|
# 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 ...egress import EgressPlan
|
||||||
from ...git_gate import GitGatePlan
|
from ...git_gate import GitGatePlan
|
||||||
from ...orchestrator.client import OrchestratorClient
|
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.lifecycle import INFRA_NAME, OrchestratorService
|
||||||
from ...orchestrator.secret_store import ENV_VAR_SECRET_NAME
|
from ...orchestrator.secret_store import ENV_VAR_SECRET_NAME
|
||||||
from ...orchestrator.reprovision import reprovision_bottles
|
from ...orchestrator.reprovision import reprovision_bottles
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from ...paths import (
|
|||||||
host_control_plane_token,
|
host_control_plane_token,
|
||||||
host_gateway_ca_dir,
|
host_gateway_ca_dir,
|
||||||
)
|
)
|
||||||
from ...orchestrator.gateway import (
|
from ...gateway import (
|
||||||
Gateway, GATEWAY_IMAGE, GATEWAY_NAME, GATEWAY_NETWORK, GATEWAY_DOCKERFILE,
|
Gateway, GATEWAY_IMAGE, GATEWAY_NAME, GATEWAY_NETWORK, GATEWAY_DOCKERFILE,
|
||||||
REPO_ROOT, GATEWAY_LABEL, MITMPROXY_HOME, DEFAULT_CA_TIMEOUT_SECONDS,
|
REPO_ROOT, GATEWAY_LABEL, MITMPROXY_HOME, DEFAULT_CA_TIMEOUT_SECONDS,
|
||||||
CA_POLL_SECONDS, GATEWAY_CA_CERT, GatewayError
|
CA_POLL_SECONDS, GATEWAY_CA_CERT, GatewayError
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from ..bottle_state import egress_state_dir
|
from ..bottle_state import egress_state_dir
|
||||||
from ..egress import EGRESS_ROUTES_FILENAME
|
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):
|
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_GATEWAY_DAEMONS=egress,git-http,supervise \\
|
||||||
BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{CONTROL_PLANE_PORT} \\
|
BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{CONTROL_PLANE_PORT} \\
|
||||||
BOT_BOTTLE_CONTROL_AUTH_JWT="$GW_JWT" \\
|
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.
|
# Reap as PID 1; children are backgrounded, so `wait` blocks.
|
||||||
while : ; do wait ; done
|
while : ; do wait ; done
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ...orchestrator.gateway import GatewayError
|
from ...gateway import GatewayError
|
||||||
from . import util as container_mod
|
from . import util as container_mod
|
||||||
|
|
||||||
# The shared host-only network the infra container and every agent bottle sit
|
# 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 pathlib import Path
|
||||||
|
|
||||||
from ... import log
|
from ... import log
|
||||||
from ...orchestrator.gateway import GATEWAY_CA_CERT, MITMPROXY_HOME
|
from ...gateway import GATEWAY_CA_CERT, MITMPROXY_HOME
|
||||||
from ...orchestrator.lifecycle import (
|
from ...orchestrator.lifecycle import (
|
||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
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).
|
# control-plane RPC and never opens bot-bottle.db (PRD 0070 / #469).
|
||||||
f"( cd /app && BOT_BOTTLE_GATEWAY_DAEMONS={_GATEWAY_DAEMONS} "
|
f"( cd /app && BOT_BOTTLE_GATEWAY_DAEMONS={_GATEWAY_DAEMONS} "
|
||||||
f"BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{port} "
|
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"
|
"while : ; do wait ; done\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ from ...git_gate import (
|
|||||||
provision_git_gate_dynamic_keys,
|
provision_git_gate_dynamic_keys,
|
||||||
revoke_git_gate_provisioned_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 ...image_cache import check_stale
|
||||||
from ...log import die, info, warn
|
from ...log import die, info, warn
|
||||||
from .. import BottleImages
|
from .. import BottleImages
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .egress_addon_core import (
|
from .gateway.egress_addon_core import (
|
||||||
ON_MATCH_REDACT,
|
ON_MATCH_REDACT,
|
||||||
HeaderMatch as CoreHeaderMatch,
|
HeaderMatch as CoreHeaderMatch,
|
||||||
MatchEntry as CoreMatchEntry,
|
MatchEntry as CoreMatchEntry,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import typing
|
|||||||
from mitmproxy import http # type: ignore[import-not-found] # pylint: disable=import-error
|
from mitmproxy import http # type: ignore[import-not-found] # pylint: disable=import-error
|
||||||
|
|
||||||
from bot_bottle.constants import IDENTITY_HEADER
|
from bot_bottle.constants import IDENTITY_HEADER
|
||||||
from bot_bottle.dlp_detectors import redact_tokens, strip_crlf
|
from bot_bottle.gateway.dlp_detectors import redact_tokens, strip_crlf
|
||||||
from bot_bottle.egress_addon_core import (
|
from bot_bottle.gateway.egress_addon_core import (
|
||||||
LOG_BLOCKS,
|
LOG_BLOCKS,
|
||||||
LOG_FULL,
|
LOG_FULL,
|
||||||
DEFAULT_OUTBOUND_ON_MATCH,
|
DEFAULT_OUTBOUND_ON_MATCH,
|
||||||
@@ -40,7 +40,7 @@ from bot_bottle.egress_addon_core import (
|
|||||||
scan_inbound,
|
scan_inbound,
|
||||||
scan_outbound,
|
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 (
|
from bot_bottle.supervise_types import (
|
||||||
STATUS_APPROVED,
|
STATUS_APPROVED,
|
||||||
STATUS_MODIFIED,
|
STATUS_MODIFIED,
|
||||||
@@ -16,7 +16,7 @@ import re
|
|||||||
import typing
|
import typing
|
||||||
from dataclasses import dataclass
|
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
|
# DLP detector-config parsing lives in a sibling module. Re-exported below
|
||||||
# so existing `from egress_addon_core import ON_MATCH_*` callers keep working.
|
# 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("egress", ("/bin/sh", "/app/egress-entrypoint.sh")),
|
||||||
_DaemonSpec("git-gate", ("/bin/sh", "/git-gate-entrypoint.sh")),
|
_DaemonSpec("git-gate", ("/bin/sh", "/git-gate-entrypoint.sh")),
|
||||||
_DaemonSpec("git-http", ("python3", "-m", "bot_bottle.git_http_backend")),
|
_DaemonSpec("git-http", ("python3", "-m", "bot_bottle.gateway.git_http_backend")),
|
||||||
_DaemonSpec("supervise", ("python3", "-m", "bot_bottle.supervise_server")),
|
_DaemonSpec("supervise", ("python3", "-m", "bot_bottle.gateway.supervise_server")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ import shlex
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
from ..constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||||
from .manifest import ManifestBottle, ManifestGitEntry
|
from ..manifest import ManifestBottle, ManifestGitEntry
|
||||||
|
|
||||||
# Short network alias for git-gate inside the gateway. The
|
# Short network alias for git-gate inside the gateway. The
|
||||||
# agent's `.gitconfig` insteadOf rewrites resolve through this name.
|
# 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
|
# identity_token), resolved server-side, so the proposal lands under the
|
||||||
# calling bottle exactly as a direct write once did.
|
# calling bottle exactly as a direct write once did.
|
||||||
try:
|
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
|
from bot_bottle.supervise_types import TOOL_GITLEAKS_ALLOW
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from policy_resolver import PolicyResolver, PolicyResolveError
|
from policy_resolver import PolicyResolver, PolicyResolveError
|
||||||
@@ -374,7 +374,7 @@ import sys
|
|||||||
# Non-blocking poll over the control plane. A decided proposal is archived
|
# Non-blocking poll over the control plane. A decided proposal is archived
|
||||||
# server-side on read, so no separate archive step is needed here.
|
# server-side on read, so no separate archive step is needed here.
|
||||||
try:
|
try:
|
||||||
from bot_bottle.policy_resolver import PolicyResolver, PolicyResolveError
|
from bot_bottle.gateway.policy_resolver import PolicyResolver, PolicyResolveError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from policy_resolver import PolicyResolver, PolicyResolveError
|
from policy_resolver import PolicyResolver, PolicyResolveError
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ from pathlib import Path
|
|||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from bot_bottle.constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
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
|
DEFAULT_PORT = 9420
|
||||||
@@ -58,10 +58,10 @@ import typing
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from bot_bottle.constants import IDENTITY_HEADER
|
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,
|
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
|
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
|
# Rendering and the deploy-key lifecycle live in sibling modules; the
|
||||||
# names are re-exported here (see __all__) so existing
|
# names are re-exported here (see __all__) so existing
|
||||||
# `from bot_bottle.git_gate import …` callers are unchanged.
|
# `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_HOSTNAME,
|
||||||
GIT_GATE_TIMEOUT_SECS,
|
GIT_GATE_TIMEOUT_SECS,
|
||||||
GitGateUpstream,
|
GitGateUpstream,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from .bottle_state import globalize_slug
|
|||||||
from .errors import MissingEnvVarError
|
from .errors import MissingEnvVarError
|
||||||
from .log import info
|
from .log import info
|
||||||
from .manifest import ManifestBottle, ManifestGitEntry
|
from .manifest import ManifestBottle, ManifestGitEntry
|
||||||
from .git_gate_render import GitGateUpstream
|
from .gateway.git_gate_render import GitGateUpstream
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .git_gate import GitGatePlan
|
from .git_gate import GitGatePlan
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from .broker import (
|
|||||||
verify_request,
|
verify_request,
|
||||||
)
|
)
|
||||||
from .docker_broker import DockerBroker, DockerBrokerError
|
from .docker_broker import DockerBroker, DockerBrokerError
|
||||||
from .gateway import Gateway, GatewayError
|
from ..gateway import Gateway, GatewayError
|
||||||
from .service import Orchestrator
|
from .service import Orchestrator
|
||||||
from .control_plane import ControlPlaneServer, dispatch, make_server
|
from .control_plane import ControlPlaneServer, dispatch, make_server
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from ..paths import (
|
|||||||
host_control_plane_token,
|
host_control_plane_token,
|
||||||
host_gateway_ca_dir,
|
host_gateway_ca_dir,
|
||||||
)
|
)
|
||||||
from .gateway import (
|
from ..gateway import (
|
||||||
GATEWAY_DOCKERFILE,
|
GATEWAY_DOCKERFILE,
|
||||||
GATEWAY_IMAGE,
|
GATEWAY_IMAGE,
|
||||||
GATEWAY_NETWORK,
|
GATEWAY_NETWORK,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from ..docker_cmd import run_docker
|
from ..docker_cmd import run_docker
|
||||||
from ..paths import host_gateway_ca_dir
|
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
|
from .lifecycle import INFRA_NAME
|
||||||
|
|
||||||
# The containers whose mitmproxy would still be serving the old CA from memory:
|
# The containers whose mitmproxy would still be serving the old CA from memory:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Per-bottle supervise plane (PRD 0013).
|
"""Per-bottle supervise plane (PRD 0013).
|
||||||
|
|
||||||
The supervise plane is the per-bottle MCP daemon plus its host-side
|
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
|
sits on the bottle's internal network and exposes MCP tools the agent
|
||||||
calls when it needs an operator-reviewed egress change:
|
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
|
This module defines the host-side library: dataclasses for the queue
|
||||||
record shapes, queue read/write helpers, the audit log writer, and the
|
record shapes, queue read/write helpers, the audit log writer, and the
|
||||||
diff renderer. The in-gateway daemon lives in
|
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).
|
lifecycle is owned by the gateway (PRD 0024).
|
||||||
|
|
||||||
For 0013 the supervisor's approval handlers are deliberately no-ops:
|
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.
|
# Probe that the package imports resolve inside the image.
|
||||||
rc, out = self._run_in_image(
|
rc, out = self._run_in_image(
|
||||||
"python3", "-c",
|
"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.assertEqual(0, rc, msg=out)
|
||||||
self.assertIn("ok", 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.egress import EGRESS_PORT
|
||||||
from bot_bottle.backend.docker.gateway_net import next_free_ip
|
from bot_bottle.backend.docker.gateway_net import next_free_ip
|
||||||
from bot_bottle.orchestrator.client import OrchestratorClient
|
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 bot_bottle.orchestrator.lifecycle import OrchestratorService
|
||||||
from tests._docker import skip_unless_docker
|
from tests._docker import skip_unless_docker
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import base64
|
|||||||
import gzip
|
import gzip
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bot_bottle.dlp_detectors import (
|
from bot_bottle.gateway.dlp_detectors import (
|
||||||
ENTROPY_BLOCK_THRESHOLD,
|
ENTROPY_BLOCK_THRESHOLD,
|
||||||
PARTIAL_MATCH_MIN_LEN,
|
PARTIAL_MATCH_MIN_LEN,
|
||||||
REDACT,
|
REDACT,
|
||||||
@@ -465,17 +465,17 @@ class TestMatchedAndSafeTokens(unittest.TestCase):
|
|||||||
|
|
||||||
class TestStripCrlf(unittest.TestCase):
|
class TestStripCrlf(unittest.TestCase):
|
||||||
def test_removes_url_encoded_crlf(self):
|
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")
|
out = strip_crlf("next=%0d%0aX-Injected: evil")
|
||||||
self.assertNotRegex(out, r"%0[dD]%0[aA]")
|
self.assertNotRegex(out, r"%0[dD]%0[aA]")
|
||||||
|
|
||||||
def test_removes_literal_header_injection(self):
|
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")
|
out = strip_crlf("value\r\nX-Injected: evil")
|
||||||
self.assertIsNone(scan_crlf_injection(out))
|
self.assertIsNone(scan_crlf_injection(out))
|
||||||
|
|
||||||
def test_leaves_clean_text_unchanged(self):
|
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"))
|
self.assertEqual("/api/v1/data?q=hello", strip_crlf("/api/v1/data?q=hello"))
|
||||||
|
|
||||||
class TestAlnumProjection(unittest.TestCase):
|
class TestAlnumProjection(unittest.TestCase):
|
||||||
|
|||||||
+11
-11
@@ -344,7 +344,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
self.assertEqual([], parse_yaml_subset(rendered)["routes"])
|
self.assertEqual([], parse_yaml_subset(rendered)["routes"])
|
||||||
|
|
||||||
def test_round_trip_through_addon_core(self):
|
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([
|
b = _bottle([
|
||||||
{"host": "api.github.com",
|
{"host": "api.github.com",
|
||||||
"auth": {"scheme": "Bearer", "token_ref": "GH_PAT"},
|
"auth": {"scheme": "Bearer", "token_ref": "GH_PAT"},
|
||||||
@@ -363,7 +363,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
self.assertEqual("", addon_routes[2].auth_scheme)
|
self.assertEqual("", addon_routes[2].auth_scheme)
|
||||||
|
|
||||||
def test_dlp_round_trips(self):
|
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": {
|
b = _bottle([{"host": "x.example", "dlp": {
|
||||||
"outbound_detectors": ["token_patterns"],
|
"outbound_detectors": ["token_patterns"],
|
||||||
"inbound_detectors": False,
|
"inbound_detectors": False,
|
||||||
@@ -375,7 +375,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
self.assertEqual((), addon_routes[0].inbound_detectors)
|
self.assertEqual((), addon_routes[0].inbound_detectors)
|
||||||
|
|
||||||
def test_outbound_on_match_round_trips(self):
|
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": {
|
b = _bottle([{"host": "logs.example", "dlp": {
|
||||||
"outbound_on_match": "redact",
|
"outbound_on_match": "redact",
|
||||||
}}])
|
}}])
|
||||||
@@ -392,7 +392,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
self.assertNotIn("outbound_on_match", rendered)
|
self.assertNotIn("outbound_on_match", rendered)
|
||||||
|
|
||||||
def test_git_fetch_policy_round_trips(self):
|
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}}])
|
b = _bottle([{"host": "github.com", "git": {"fetch": True}}])
|
||||||
routes = egress_routes_for_bottle(b)
|
routes = egress_routes_for_bottle(b)
|
||||||
rendered = egress_render_routes(routes)
|
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
|
it, but the renderer in between dropped it — so the flag never reached
|
||||||
the proxy and registry pulls kept failing with "unauthorized" while
|
the proxy and registry pulls kept failing with "unauthorized" while
|
||||||
the config looked correct everywhere it was inspected."""
|
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}])
|
b = _bottle([{"host": "registry-1.docker.io", "preserve_auth": True}])
|
||||||
routes = egress_routes_for_bottle(b)
|
routes = egress_routes_for_bottle(b)
|
||||||
rendered = egress_render_routes(routes)
|
rendered = egress_render_routes(routes)
|
||||||
@@ -416,7 +416,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
b = _bottle([{"host": "x.example"}])
|
b = _bottle([{"host": "x.example"}])
|
||||||
rendered = egress_render_routes(egress_routes_for_bottle(b))
|
rendered = egress_render_routes(egress_routes_for_bottle(b))
|
||||||
self.assertNotIn("preserve_auth", rendered)
|
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)
|
self.assertFalse(load_config(rendered).routes[0].preserve_auth)
|
||||||
|
|
||||||
def test_log_zero_omitted_from_render(self):
|
def test_log_zero_omitted_from_render(self):
|
||||||
@@ -434,7 +434,7 @@ class TestRenderRoutes(unittest.TestCase):
|
|||||||
self.assertTrue(rendered.startswith(f"log: {level}\n"))
|
self.assertTrue(rendered.startswith(f"log: {level}\n"))
|
||||||
|
|
||||||
def test_log_level_round_trips_to_addon_core(self):
|
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"}])
|
b = _bottle([{"host": "x.example"}])
|
||||||
routes = egress_routes_for_bottle(b)
|
routes = egress_routes_for_bottle(b)
|
||||||
rendered = egress_render_routes(routes, log=LOG_FULL)
|
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):
|
def test_log_via_manifest_flows_to_render(self):
|
||||||
from bot_bottle.manifest import ManifestIndex
|
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({
|
m = ManifestIndex.from_json_obj({
|
||||||
"bottles": {"dev": {"egress": {
|
"bottles": {"dev": {"egress": {
|
||||||
"log": 1,
|
"log": 1,
|
||||||
@@ -512,7 +512,7 @@ class TestRenderRoutesEscaping(unittest.TestCase):
|
|||||||
self.assertEqual('Bear"er', parsed[0]["inspect"]["auth_scheme"])
|
self.assertEqual('Bear"er', parsed[0]["inspect"]["auth_scheme"])
|
||||||
|
|
||||||
def test_path_value_with_double_quote_round_trips(self):
|
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(
|
routes = (EgressRoute(
|
||||||
host="api.example",
|
host="api.example",
|
||||||
matches=(MatchEntry(paths=(PathMatch(type="prefix", value='/v1/"quoted"/'),)),),
|
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"])
|
self.assertEqual('/v1/"quoted"/', parsed[0]["inspect"]["matches"][0]["paths"][0]["value"])
|
||||||
|
|
||||||
def test_header_value_with_double_quote_round_trips(self):
|
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(
|
routes = (EgressRoute(
|
||||||
host="api.example",
|
host="api.example",
|
||||||
matches=(MatchEntry(headers=(HeaderMatch(name="x-h", value='val"ue'),)),),
|
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)
|
self.assertNotEqual(plan_a.canary, plan_b.canary)
|
||||||
|
|
||||||
def test_canary_detected_by_scan_known_secrets(self):
|
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()
|
plan = self._make_plan()
|
||||||
env = {plan.canary_env: plan.canary}
|
env = {plan.canary_env: plan.canary}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import unittest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from bot_bottle.egress_addon_core import (
|
from bot_bottle.gateway.egress_addon_core import (
|
||||||
LOG_BLOCKS,
|
LOG_BLOCKS,
|
||||||
LOG_FULL,
|
LOG_FULL,
|
||||||
LOG_OFF,
|
LOG_OFF,
|
||||||
@@ -1377,15 +1377,15 @@ class TestScanOutboundEnhanced(unittest.TestCase):
|
|||||||
|
|
||||||
class TestOutboundDetectorNames(unittest.TestCase):
|
class TestOutboundDetectorNames(unittest.TestCase):
|
||||||
def test_entropy_in_outbound_detector_names(self):
|
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)
|
self.assertIn("entropy", OUTBOUND_DETECTOR_NAMES)
|
||||||
|
|
||||||
def test_known_secrets_in_outbound_detector_names(self):
|
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)
|
self.assertIn("known_secrets", OUTBOUND_DETECTOR_NAMES)
|
||||||
|
|
||||||
def test_token_patterns_in_outbound_detector_names(self):
|
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)
|
self.assertIn("token_patterns", OUTBOUND_DETECTOR_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ def _ensure_shims() -> None:
|
|||||||
|
|
||||||
_ensure_shims()
|
_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()
|
_ensure_shims()
|
||||||
|
|
||||||
import bot_bottle.egress_addon as _ea_mod # noqa: E402 (after shims)
|
import bot_bottle.gateway.egress_addon as _ea_mod # noqa: E402 (after shims)
|
||||||
from bot_bottle.egress_addon import EgressAddon # noqa: E402 (after shims)
|
from bot_bottle.gateway.egress_addon import EgressAddon # noqa: E402 (after shims)
|
||||||
from bot_bottle.egress_addon import ( # noqa: E402
|
from bot_bottle.gateway.egress_addon import ( # noqa: E402
|
||||||
DEFAULT_INBOUND_SCAN_LIMIT_BYTES,
|
DEFAULT_INBOUND_SCAN_LIMIT_BYTES,
|
||||||
DEFAULT_TOKEN_ALLOW_TIMEOUT_SECONDS,
|
DEFAULT_TOKEN_ALLOW_TIMEOUT_SECONDS,
|
||||||
_inbound_scan_limit_from_env,
|
_inbound_scan_limit_from_env,
|
||||||
_token_allow_timeout_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,
|
Config,
|
||||||
LOG_BLOCKS,
|
LOG_BLOCKS,
|
||||||
LOG_FULL,
|
LOG_FULL,
|
||||||
Route,
|
Route,
|
||||||
route_to_yaml_dict,
|
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
|
import unittest
|
||||||
|
|
||||||
from bot_bottle.egress_addon_core import (
|
from bot_bottle.gateway.egress_addon_core import (
|
||||||
HeaderMatch,
|
HeaderMatch,
|
||||||
MatchEntry,
|
MatchEntry,
|
||||||
PathMatch,
|
PathMatch,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bot_bottle.egress_addon_core import (
|
from bot_bottle.gateway.egress_addon_core import (
|
||||||
DENY_RESOLVER_ERROR,
|
DENY_RESOLVER_ERROR,
|
||||||
DENY_UNATTRIBUTED,
|
DENY_UNATTRIBUTED,
|
||||||
DENY_UNPARSEABLE,
|
DENY_UNPARSEABLE,
|
||||||
@@ -12,7 +12,7 @@ from bot_bottle.egress_addon_core import (
|
|||||||
resolve_client_config,
|
resolve_client_config,
|
||||||
resolve_client_context,
|
resolve_client_context,
|
||||||
)
|
)
|
||||||
from bot_bottle.policy_resolver import PolicyResolveError
|
from bot_bottle.gateway.policy_resolver import PolicyResolveError
|
||||||
|
|
||||||
|
|
||||||
class _FakeResolver:
|
class _FakeResolver:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class TestBuildInfraRootfs(unittest.TestCase):
|
|||||||
self.assertIn("bot_bottle.orchestrator", init)
|
self.assertIn("bot_bottle.orchestrator", init)
|
||||||
# Gateway launches via the installed package (there is no
|
# Gateway launches via the installed package (there is no
|
||||||
# /app/gateway_init.py file since the daemons moved into bot_bottle).
|
# /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)
|
self.assertIn("export PATH=", init)
|
||||||
# Persistent registry volume mounted at the DB dir before the CP starts.
|
# Persistent registry volume mounted at the DB dir before the CP starts.
|
||||||
self.assertIn("/dev/vdb", init)
|
self.assertIn("/dev/vdb", init)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Unit: gateway data-plane init supervisor (PRD 0070; PRD 0024 bundle shape).
|
"""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
|
and the supervisor's end-to-end signal / exit-code behavior. The
|
||||||
end-to-end tests use real subprocesses (`sleep`, `/bin/sh -c '...'`) —
|
end-to-end tests use real subprocesses (`sleep`, `/bin/sh -c '...'`) —
|
||||||
short-lived, no docker required — so they run under `tests/unit/`
|
short-lived, no docker required — so they run under `tests/unit/`
|
||||||
@@ -18,7 +18,7 @@ import warnings
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from bot_bottle.gateway_init import (
|
from bot_bottle.gateway.gateway_init import (
|
||||||
_DaemonSpec,
|
_DaemonSpec,
|
||||||
_Supervisor,
|
_Supervisor,
|
||||||
_argv_for_daemon,
|
_argv_for_daemon,
|
||||||
@@ -489,7 +489,7 @@ class TestSupervisor(unittest.TestCase):
|
|||||||
time.sleep(0.3) # let `trap` register
|
time.sleep(0.3) # let `trap` register
|
||||||
sup.request_shutdown(reason="test")
|
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)
|
rc = self._drive(sup, max_wait_s=4.0)
|
||||||
|
|
||||||
# Process was SIGKILL'd → returncode -9 on POSIX.
|
# Process was SIGKILL'd → returncode -9 on POSIX.
|
||||||
@@ -531,7 +531,7 @@ class TestMainEndToEnd(unittest.TestCase):
|
|||||||
|
|
||||||
helper = (
|
helper = (
|
||||||
"import os, runpy, sys\n"
|
"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"
|
"si._DAEMONS = (\n"
|
||||||
f" si._DaemonSpec('alpha', ({SLEEP!r},'30')),\n"
|
f" si._DaemonSpec('alpha', ({SLEEP!r},'30')),\n"
|
||||||
f" si._DaemonSpec('beta', ({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
|
# execute from the bare repo directory, so the embedded Python must
|
||||||
# include /app and support both import layouts.
|
# include /app and support both import layouts.
|
||||||
self.assertIn('PYTHONPATH="/app${PYTHONPATH:+:$PYTHONPATH}"', hook)
|
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)
|
self.assertIn("from policy_resolver import PolicyResolver", hook)
|
||||||
|
|
||||||
def test_inline_gitleaks_allow_fails_closed_without_supervisor(self):
|
def test_inline_gitleaks_allow_fails_closed_without_supervisor(self):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bot_bottle.git_gate_render import (
|
from bot_bottle.gateway.git_gate_render import (
|
||||||
GitGateUpstream,
|
GitGateUpstream,
|
||||||
git_gate_render_entrypoint,
|
git_gate_render_entrypoint,
|
||||||
git_gate_render_provision,
|
git_gate_render_provision,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from bot_bottle.git_gate import GIT_GATE_TIMEOUT_SECS
|
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
|
# 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""),
|
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
|
||||||
]
|
]
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"bot_bottle.git_http_backend.subprocess.run",
|
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||||
side_effect=calls,
|
side_effect=calls,
|
||||||
) as run:
|
) as run:
|
||||||
request = urllib.request.Request(
|
request = urllib.request.Request(
|
||||||
@@ -265,7 +265,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
|||||||
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
|
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
|
||||||
]
|
]
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"bot_bottle.git_http_backend.subprocess.run",
|
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||||
side_effect=calls,
|
side_effect=calls,
|
||||||
) as run:
|
) as run:
|
||||||
req = urllib.request.Request(
|
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"
|
denial = b"git-gate: upstream fetch failed; refusing to serve stale data\n"
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"bot_bottle.git_http_backend.subprocess.run",
|
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||||
return_value=subprocess.CompletedProcess(
|
return_value=subprocess.CompletedProcess(
|
||||||
["hook"], 1, b"", denial,
|
["hook"], 1, b"", denial,
|
||||||
),
|
),
|
||||||
@@ -355,7 +355,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
|||||||
self.addCleanup(server.server_close)
|
self.addCleanup(server.server_close)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"bot_bottle.git_http_backend.subprocess.run",
|
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||||
return_value=subprocess.CompletedProcess(
|
return_value=subprocess.CompletedProcess(
|
||||||
["hook"], 2, b"", b"",
|
["hook"], 2, b"", b"",
|
||||||
),
|
),
|
||||||
@@ -402,7 +402,7 @@ class TestGitHttpBackend(unittest.TestCase):
|
|||||||
self.addCleanup(server.server_close)
|
self.addCleanup(server.server_close)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"bot_bottle.git_http_backend.subprocess.run",
|
"bot_bottle.gateway.git_http_backend.subprocess.run",
|
||||||
side_effect=PermissionError(13, "Permission denied"),
|
side_effect=PermissionError(13, "Permission denied"),
|
||||||
):
|
):
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
@@ -461,7 +461,7 @@ class TestMalformedStatusHeader(unittest.TestCase):
|
|||||||
|
|
||||||
def _get_with_backend_response(self, cgi_response: bytes) -> int:
|
def _get_with_backend_response(self, cgi_response: bytes) -> int:
|
||||||
with mock.patch(
|
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),
|
return_value=mock.Mock(returncode=0, stdout=cgi_response),
|
||||||
):
|
):
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
@@ -545,7 +545,7 @@ class TestContentLengthBounds(unittest.TestCase):
|
|||||||
# With a valid Content-Length the handler proceeds into
|
# With a valid Content-Length the handler proceeds into
|
||||||
# git http-backend; that will fail (no real git repo) but the
|
# git http-backend; that will fail (no real git repo) but the
|
||||||
# status won't be 400 or 413.
|
# 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(
|
run.return_value = mock.Mock(
|
||||||
returncode=0,
|
returncode=0,
|
||||||
stdout=(
|
stdout=(
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ from __future__ import annotations
|
|||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bot_bottle.git_http_backend import resolve_sandbox_root
|
from bot_bottle.gateway.git_http_backend import resolve_sandbox_root
|
||||||
from bot_bottle.policy_resolver import PolicyResolveError
|
from bot_bottle.gateway.policy_resolver import PolicyResolveError
|
||||||
|
|
||||||
_BASE = Path("/git")
|
_BASE = Path("/git")
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class TestInfraRun(unittest.TestCase):
|
|||||||
self.assertIn("bot_bottle.orchestrator", script)
|
self.assertIn("bot_bottle.orchestrator", script)
|
||||||
# Gateway launches via the installed package (there is no
|
# Gateway launches via the installed package (there is no
|
||||||
# /app/gateway_init.py file since the daemons moved into bot_bottle).
|
# /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
|
self.assertIn("127.0.0.1", script) # they reach each other on loopback
|
||||||
|
|
||||||
def test_db_is_a_container_only_volume(self) -> None:
|
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 unittest.mock import Mock, patch
|
||||||
|
|
||||||
from bot_bottle.backend.docker.gateway import DockerGateway
|
from bot_bottle.backend.docker.gateway import DockerGateway
|
||||||
from bot_bottle.orchestrator.gateway import (
|
from bot_bottle.gateway import (
|
||||||
GATEWAY_CA_CERT,
|
GATEWAY_CA_CERT,
|
||||||
GATEWAY_NAME,
|
GATEWAY_NAME,
|
||||||
GatewayError,
|
GatewayError,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import urllib.error
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, Mock, patch
|
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 (
|
from bot_bottle.orchestrator.lifecycle import (
|
||||||
INFRA_NAME,
|
INFRA_NAME,
|
||||||
INFRA_SOURCE_HASH_LABEL,
|
INFRA_SOURCE_HASH_LABEL,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bot_bottle.egress import EgressPlan, EgressRoute
|
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 (
|
from bot_bottle.orchestrator.registration import (
|
||||||
RegistrationInputs,
|
RegistrationInputs,
|
||||||
egress_policy,
|
egress_policy,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from bot_bottle.orchestrator import rotate_ca
|
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.orchestrator.lifecycle import INFRA_NAME
|
||||||
from bot_bottle.paths import host_gateway_ca_dir
|
from bot_bottle.paths import host_gateway_ca_dir
|
||||||
from tests.unit import use_bottle_root
|
from tests.unit import use_bottle_root
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
import urllib.error
|
import urllib.error
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from bot_bottle.policy_resolver import (
|
from bot_bottle.gateway.policy_resolver import (
|
||||||
CONTROL_AUTH_HEADER,
|
CONTROL_AUTH_HEADER,
|
||||||
CONTROL_AUTH_JWT_ENV,
|
CONTROL_AUTH_JWT_ENV,
|
||||||
PolicyResolveError,
|
PolicyResolveError,
|
||||||
@@ -15,7 +15,7 @@ from bot_bottle.policy_resolver import (
|
|||||||
_control_auth_headers,
|
_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:
|
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 queue_store as _qs
|
||||||
from bot_bottle.store import audit_store as _as
|
from bot_bottle.store import audit_store as _as
|
||||||
|
|
||||||
from bot_bottle import supervise_server # noqa: E402
|
from bot_bottle.gateway import supervise_server # noqa: E402
|
||||||
from bot_bottle.supervise_server import (
|
from bot_bottle.gateway.supervise_server import (
|
||||||
ERR_INTERNAL,
|
ERR_INTERNAL,
|
||||||
ERR_INVALID_PARAMS,
|
ERR_INVALID_PARAMS,
|
||||||
ERR_INVALID_REQUEST,
|
ERR_INVALID_REQUEST,
|
||||||
|
|||||||
Reference in New Issue
Block a user