ba25845886
tracker-policy-pr / check-pr (pull_request) Successful in 12s
lint / lint (push) Successful in 51s
test / integration-docker (pull_request) Successful in 40s
test / unit (pull_request) Successful in 46s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 43s
test / publish-infra (pull_request) Has been skipped
Adds the `nested_containers` bottle flag. On the macOS backend it starts a rootless podman service inside the bottle and exposes its Docker-compatible API socket, so the agent still runs `docker` and `docker compose`. No host daemon socket is mounted and the guest gains no capabilities; backends that cannot run a guest-local engine reject the flag in the shared prepare template rather than ignoring it. Podman rather than rootless Docker because Apple Container's capability bounding set omits CAP_SYS_ADMIN, which the kernel requires to write a multi-range uid_map via newuidmap. With no subordinate UID range podman falls back to a single-UID self-mapping an unprivileged process may write itself, so the image build strips /etc/subuid and /etc/subgid entries rather than adding them. That mapping is also why nested containers are not an isolation layer: root inside one is the agent user outside it. They are a build/test convenience; the bottle remains the boundary. Ports the spike branch onto main, renaming docker_access — it implied Docker and granted access to nothing on the host — and drops podman from the derived layer now that every built-in image ships it (#451). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
"""MacosContainerBottleBackend — Apple Container implementation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Generator, Sequence
|
|
|
|
from ...agent_provider import AgentProvisionPlan
|
|
from ...egress import EgressPlan
|
|
from ...env import ResolvedEnv
|
|
from ...git_gate import GitGatePlan
|
|
from ...supervise import SupervisePlan
|
|
from ...manifest import Manifest
|
|
from .. import ActiveAgent, BottleBackend, BottleImages, BottleSpec
|
|
from . import cleanup as _cleanup
|
|
from . import enumerate as _enumerate
|
|
from . import launch as _launch
|
|
from . import resolve_plan as _resolve_plan
|
|
from . import util as _container
|
|
from .bottle import MacosContainerBottle
|
|
from .bottle_cleanup_plan import MacosContainerBottleCleanupPlan
|
|
from .bottle_plan import MacosContainerBottlePlan
|
|
|
|
|
|
class MacosContainerBottleBackend(
|
|
BottleBackend["MacosContainerBottlePlan", "MacosContainerBottleCleanupPlan"]
|
|
):
|
|
"""Apple Container backend. Selected by
|
|
`BOT_BOTTLE_BACKEND=macos-container` or
|
|
`--backend=macos-container`."""
|
|
|
|
name = "macos-container"
|
|
supports_nested_containers = True
|
|
|
|
@classmethod
|
|
def is_available(cls) -> bool:
|
|
return _container.is_available()
|
|
|
|
@classmethod
|
|
def setup(cls) -> int:
|
|
from . import setup as _setup
|
|
return _setup.setup()
|
|
|
|
@classmethod
|
|
def status(cls) -> int:
|
|
from . import setup as _setup
|
|
return _setup.status()
|
|
|
|
@classmethod
|
|
def teardown(cls) -> int:
|
|
from . import setup as _setup
|
|
return _setup.teardown()
|
|
|
|
def _preflight(self) -> None:
|
|
_resolve_plan.preflight()
|
|
|
|
def _build_guest_env(self, resolved_env: ResolvedEnv) -> dict[str, str]:
|
|
return _resolve_plan.build_guest_env(resolved_env)
|
|
|
|
def _resolve_plan(
|
|
self,
|
|
spec: BottleSpec,
|
|
*,
|
|
manifest: Manifest,
|
|
slug: str,
|
|
resolved_env: ResolvedEnv,
|
|
agent_provision_plan: AgentProvisionPlan,
|
|
egress_plan: EgressPlan,
|
|
git_gate_plan: GitGatePlan,
|
|
supervise_plan: SupervisePlan | None,
|
|
stage_dir: Path,
|
|
) -> MacosContainerBottlePlan:
|
|
return _resolve_plan.resolve_plan(
|
|
spec,
|
|
manifest=manifest,
|
|
slug=slug,
|
|
resolved_env=resolved_env,
|
|
agent_provision_plan=agent_provision_plan,
|
|
egress_plan=egress_plan,
|
|
supervise_plan=supervise_plan,
|
|
git_gate_plan=git_gate_plan,
|
|
stage_dir=stage_dir,
|
|
)
|
|
|
|
def prelaunch_checks(self, plan: MacosContainerBottlePlan) -> None:
|
|
_launch.stale_checks(plan)
|
|
|
|
def _build_or_load_images(self, plan: MacosContainerBottlePlan) -> BottleImages:
|
|
return _launch.build_or_load_images(plan)
|
|
|
|
@contextmanager
|
|
def _launch_impl(
|
|
self, plan: MacosContainerBottlePlan, images: BottleImages
|
|
) -> Generator[MacosContainerBottle, None, None]:
|
|
with _launch.launch(plan, images, provision=self.provision) as bottle:
|
|
yield bottle
|
|
|
|
def ensure_orchestrator(self) -> str:
|
|
"""Bring up the per-host infra container (control plane + gateway) and
|
|
return its control-plane URL — the on-demand entry point operator tools
|
|
(`supervise`) call when no control plane is running yet. Mirrors
|
|
firecracker's infra-VM bring-up."""
|
|
from .infra import MacosInfraService
|
|
return MacosInfraService().ensure_running().control_plane_url
|
|
|
|
def prepare_cleanup(self) -> MacosContainerBottleCleanupPlan:
|
|
return _cleanup.prepare_cleanup()
|
|
|
|
def cleanup(self, plan: MacosContainerBottleCleanupPlan) -> None:
|
|
_cleanup.cleanup(plan)
|
|
|
|
def enumerate_active(self) -> Sequence[ActiveAgent]:
|
|
return _enumerate.enumerate_active()
|
|
|
|
def supervise_mcp_url(self, plan: MacosContainerBottlePlan) -> str:
|
|
return plan.agent_supervise_url
|