fix: restore runtime workspace provisioning
This commit is contained in:
@@ -226,24 +226,10 @@ class AgentProvider(ABC):
|
|||||||
def provision_git(self, bottle: "Bottle", plan: "BottlePlan") -> None:
|
def provision_git(self, bottle: "Bottle", plan: "BottlePlan") -> None:
|
||||||
"""Configure git inside the agent container.
|
"""Configure git inside the agent container.
|
||||||
|
|
||||||
Default: Debian/node — copies .git when --cwd is set, writes the
|
Default: Debian/node — writes the git-gate insteadOf gitconfig
|
||||||
git-gate insteadOf gitconfig, sets user.name/email as node.
|
and sets user.name/email as node. Workspace copy runs through
|
||||||
Override for images that run as a different user or use a
|
BottleBackend.provision_workspace against the running bottle."""
|
||||||
non-standard home directory."""
|
|
||||||
from .log import info
|
from .log import info
|
||||||
# FIXME: re-enable workspace planning
|
|
||||||
# workspace = plan.workspace_plan
|
|
||||||
# if workspace.enabled and workspace.copy_git and workspace.has_host_git_dir:
|
|
||||||
# guest_workspace_git = f"{workspace.guest_path}/.git"
|
|
||||||
# host_git = str(workspace.host_path / ".git")
|
|
||||||
# info(f"copying {host_git} -> {bottle.name}:{guest_workspace_git}")
|
|
||||||
# bottle.exec(f"mkdir -p {shlex.quote(workspace.guest_path)}", user="root")
|
|
||||||
# bottle.cp_in(host_git, guest_workspace_git)
|
|
||||||
# bottle.exec(
|
|
||||||
# f"chown -R {shlex.quote(workspace.owner)} "
|
|
||||||
# f"{shlex.quote(guest_workspace_git)}",
|
|
||||||
# user="root",
|
|
||||||
# )
|
|
||||||
|
|
||||||
manifest_bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name)
|
manifest_bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name)
|
||||||
if manifest_bottle.git:
|
if manifest_bottle.git:
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ manifest does not carry a backend field; the host picks.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from contextlib import AbstractContextManager
|
from contextlib import AbstractContextManager
|
||||||
@@ -47,7 +48,7 @@ from ..manifest import ManifestGitEntry, Manifest
|
|||||||
from ..supervise import SupervisePlan
|
from ..supervise import SupervisePlan
|
||||||
from ..util import expand_tilde
|
from ..util import expand_tilde
|
||||||
from ..env import resolve_env, ResolvedEnv
|
from ..env import resolve_env, ResolvedEnv
|
||||||
# from ..workspace import WorkspacePlan
|
from ..workspace import WorkspacePlan, workspace_plan
|
||||||
from .print_util import print_multi, visible_agent_env_names
|
from .print_util import print_multi, visible_agent_env_names
|
||||||
from .util import host_skill_dir
|
from .util import host_skill_dir
|
||||||
|
|
||||||
@@ -101,7 +102,10 @@ class BottlePlan(ABC):
|
|||||||
egress_plan: EgressPlan
|
egress_plan: EgressPlan
|
||||||
supervise_plan: SupervisePlan | None
|
supervise_plan: SupervisePlan | None
|
||||||
agent_provision: AgentProvisionPlan
|
agent_provision: AgentProvisionPlan
|
||||||
# workspace_plan: WorkspacePlan
|
|
||||||
|
@property
|
||||||
|
def workspace_plan(self) -> WorkspacePlan:
|
||||||
|
return workspace_plan(self.spec, guest_home=self.guest_home)
|
||||||
|
|
||||||
def print(self, *, remote_control: bool) -> None:
|
def print(self, *, remote_control: bool) -> None:
|
||||||
"""Render the y/N preflight summary to stderr."""
|
"""Render the y/N preflight summary to stderr."""
|
||||||
@@ -293,6 +297,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
manifest_agent_provider = manifest_bottle.agent_provider
|
manifest_agent_provider = manifest_bottle.agent_provider
|
||||||
agent_provider = get_provider(manifest_agent_provider.template)
|
agent_provider = get_provider(manifest_agent_provider.template)
|
||||||
resolved_env = resolve_env(manifest, spec.agent_name)
|
resolved_env = resolve_env(manifest, spec.agent_name)
|
||||||
|
workspace = workspace_plan(spec, guest_home=agent_provider.guest_home)
|
||||||
|
|
||||||
slug = mint_slug(spec)
|
slug = mint_slug(spec)
|
||||||
write_launch_metadata(slug, spec, compose_project="", backend=self.name)
|
write_launch_metadata(slug, spec, compose_project="", backend=self.name)
|
||||||
@@ -319,7 +324,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
forward_host_credentials=manifest_agent_provider.forward_host_credentials,
|
forward_host_credentials=manifest_agent_provider.forward_host_credentials,
|
||||||
auth_token=manifest_agent_provider.auth_token,
|
auth_token=manifest_agent_provider.auth_token,
|
||||||
host_env=dict(os.environ),
|
host_env=dict(os.environ),
|
||||||
# trusted_project_path=workspace_plan.workdir,
|
trusted_project_path=workspace.workdir,
|
||||||
label=spec.label,
|
label=spec.label,
|
||||||
color=spec.color,
|
color=spec.color,
|
||||||
)
|
)
|
||||||
@@ -448,7 +453,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
prompt_path = provider.provision_prompt(plan, bottle)
|
prompt_path = provider.provision_prompt(plan, bottle)
|
||||||
provider.provision(plan, bottle)
|
provider.provision(plan, bottle)
|
||||||
provider.provision_skills(plan, bottle)
|
provider.provision_skills(plan, bottle)
|
||||||
# self.provision_workspace(plan, bottle)
|
self.provision_workspace(plan, bottle)
|
||||||
provider.provision_git(bottle, plan)
|
provider.provision_git(bottle, plan)
|
||||||
provider.provision_supervise_mcp(
|
provider.provision_supervise_mcp(
|
||||||
plan, bottle, self.supervise_mcp_url(plan),
|
plan, bottle, self.supervise_mcp_url(plan),
|
||||||
@@ -456,9 +461,30 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
return prompt_path
|
return prompt_path
|
||||||
|
|
||||||
def provision_workspace(self, plan: PlanT, bottle: "Bottle") -> None:
|
def provision_workspace(self, plan: PlanT, bottle: "Bottle") -> None:
|
||||||
"""Copy the operator workspace into the running bottle when
|
"""Copy the operator workspace into the running bottle.
|
||||||
the backend cannot bake it into the agent image. Default is
|
|
||||||
no-op for backends like Docker that handle this before launch."""
|
This is the only supported workspace-provisioning path: Docker
|
||||||
|
does not build a derived image containing the current
|
||||||
|
workspace."""
|
||||||
|
workspace = plan.workspace_plan
|
||||||
|
if not (workspace.enabled and workspace.copy_contents):
|
||||||
|
return
|
||||||
|
|
||||||
|
guest_parent = workspace.guest_path.rsplit("/", 1)[0] or "/"
|
||||||
|
guest_path = shlex.quote(workspace.guest_path)
|
||||||
|
guest_parent = shlex.quote(guest_parent)
|
||||||
|
owner = shlex.quote(workspace.owner)
|
||||||
|
mode = shlex.quote(workspace.mode)
|
||||||
|
info(f"copying {workspace.host_path} -> {bottle.name}:{workspace.guest_path}")
|
||||||
|
bottle.exec(
|
||||||
|
f"rm -rf {guest_path} && mkdir -p {guest_parent}",
|
||||||
|
user="root",
|
||||||
|
)
|
||||||
|
bottle.cp_in(str(workspace.host_path), workspace.guest_path)
|
||||||
|
bottle.exec(
|
||||||
|
f"chown -R {owner} {guest_path} && chmod {mode} {guest_path}",
|
||||||
|
user="root",
|
||||||
|
)
|
||||||
|
|
||||||
def supervise_mcp_url(self, plan: PlanT) -> str:
|
def supervise_mcp_url(self, plan: PlanT) -> str:
|
||||||
"""Return the agent-side URL of the per-bottle supervise
|
"""Return the agent-side URL of the per-bottle supervise
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ PRD 0018 chunk 3: each instance is one `docker compose` project.
|
|||||||
|
|
||||||
The flow is:
|
The flow is:
|
||||||
|
|
||||||
1. Build the agent's base + derived image (compose builds the
|
1. Build the agent image from the provider Dockerfile (compose
|
||||||
sidecar images via the `build:` directive on first up).
|
builds the sidecar images via the `build:` directive on first up).
|
||||||
2. Mint the per-bottle egress CA (chunk 2 writes it under
|
2. Mint the per-bottle egress CA (chunk 2 writes it under
|
||||||
state/<slug>/egress/).
|
state/<slug>/egress/).
|
||||||
3. Populate the inner plans with launch-time fields so the
|
3. Populate the inner plans with launch-time fields so the
|
||||||
@@ -15,8 +15,8 @@ The flow is:
|
|||||||
7. `docker compose up -d` (token + OAuth values flow into the
|
7. `docker compose up -d` (token + OAuth values flow into the
|
||||||
compose subprocess env so `environment: [NAME]` bare-name
|
compose subprocess env so `environment: [NAME]` bare-name
|
||||||
entries inherit without rendering values into the file).
|
entries inherit without rendering values into the file).
|
||||||
8. Provision (CA install, prompt copy, skills, git, supervise
|
8. Provision (CA install, prompt copy, skills, workspace, git,
|
||||||
config) — unchanged, uses `docker exec`.
|
supervise config) — unchanged, uses `docker exec` / `docker cp`.
|
||||||
9. Yield a DockerBottle handle. `exec_agent` runs claude via
|
9. Yield a DockerBottle handle. `exec_agent` runs claude via
|
||||||
`docker exec -it` exactly like the pre-compose world.
|
`docker exec -it` exactly like the pre-compose world.
|
||||||
|
|
||||||
|
|||||||
@@ -56,5 +56,4 @@ def resolve_plan(
|
|||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
use_runsc=use_runsc,
|
use_runsc=use_runsc,
|
||||||
agent_provision=agent_provision_plan,
|
agent_provision=agent_provision_plan,
|
||||||
# workspace_plan=workspace_plan,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ from . import smolvm as _smolvm
|
|||||||
from .bottle import SmolmachinesBottle
|
from .bottle import SmolmachinesBottle
|
||||||
from .bottle_cleanup_plan import SmolmachinesBottleCleanupPlan
|
from .bottle_cleanup_plan import SmolmachinesBottleCleanupPlan
|
||||||
from .bottle_plan import SmolmachinesBottlePlan
|
from .bottle_plan import SmolmachinesBottlePlan
|
||||||
# from .provision import workspace as _workspace
|
|
||||||
|
|
||||||
|
|
||||||
class SmolmachinesBottleBackend(
|
class SmolmachinesBottleBackend(
|
||||||
@@ -82,11 +81,6 @@ class SmolmachinesBottleBackend(
|
|||||||
with _launch.launch(plan, provision=self.provision) as bottle:
|
with _launch.launch(plan, provision=self.provision) as bottle:
|
||||||
yield bottle
|
yield bottle
|
||||||
|
|
||||||
# def provision_workspace(
|
|
||||||
# self, plan: SmolmachinesBottlePlan, bottle: Bottle
|
|
||||||
# ) -> None:
|
|
||||||
# _workspace.provision_workspace(plan, bottle)
|
|
||||||
|
|
||||||
def supervise_mcp_url(self, plan: SmolmachinesBottlePlan) -> str:
|
def supervise_mcp_url(self, plan: SmolmachinesBottlePlan) -> str:
|
||||||
"""The smolmachines guest reaches the supervise sidecar via a
|
"""The smolmachines guest reaches the supervise sidecar via a
|
||||||
host-published random port the launch step pinned earlier
|
host-published random port the launch step pinned earlier
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ the `AgentProvider` plugin under `bot_bottle/contrib/`. CA and git
|
|||||||
provisioning also moved to the AgentProvider ABC (with Debian/node
|
provisioning also moved to the AgentProvider ABC (with Debian/node
|
||||||
defaults); user plugins override them for non-standard images.
|
defaults); user plugins override them for non-standard images.
|
||||||
|
|
||||||
The module left in this subpackage handles the remaining backend-
|
No modules remain in this subpackage. Workspace copying now runs
|
||||||
specific step:
|
through `BottleBackend.provision_workspace` against the running
|
||||||
|
bottle for every backend.
|
||||||
- workspace.py — copy the operator workspace into the guest
|
|
||||||
(currently commented out — workspace planning is disabled)
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
"""Copy the operator workspace into a smolmachines guest.
|
|
||||||
|
|
||||||
DISABLED — workspace planning is currently commented out at the
|
|
||||||
BottlePlan level. This module is kept as a placeholder for when
|
|
||||||
workspace support is re-enabled.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# from __future__ import annotations
|
|
||||||
#
|
|
||||||
# import shlex
|
|
||||||
#
|
|
||||||
# from ....log import info
|
|
||||||
# from ... import Bottle
|
|
||||||
# from ..bottle_plan import SmolmachinesBottlePlan
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# def provision_workspace(plan: SmolmachinesBottlePlan, bottle: Bottle) -> None:
|
|
||||||
# """Copy host cwd contents to the planned guest workspace."""
|
|
||||||
# workspace = plan.workspace_plan
|
|
||||||
# if not (workspace.enabled and workspace.copy_contents):
|
|
||||||
# return
|
|
||||||
#
|
|
||||||
# guest_parent = workspace.guest_path.rsplit("/", 1)[0] or "/"
|
|
||||||
# guest_path_q = shlex.quote(workspace.guest_path)
|
|
||||||
# guest_parent_q = shlex.quote(guest_parent)
|
|
||||||
# owner_q = shlex.quote(workspace.owner)
|
|
||||||
# mode_q = shlex.quote(workspace.mode)
|
|
||||||
# info(f"copying {workspace.host_path} -> {bottle.name}:{workspace.guest_path}")
|
|
||||||
# bottle.exec(
|
|
||||||
# f"rm -rf {guest_path_q} && mkdir -p {guest_parent_q}",
|
|
||||||
# user="root",
|
|
||||||
# )
|
|
||||||
# bottle.cp_in(str(workspace.host_path), workspace.guest_path)
|
|
||||||
# bottle.exec(
|
|
||||||
# f"chown -R {owner_q} {guest_path_q} && chmod {mode_q} {guest_path_q}",
|
|
||||||
# user="root",
|
|
||||||
# )
|
|
||||||
@@ -18,8 +18,6 @@ from ...agent_provider import AgentProvisionPlan
|
|||||||
from ...egress import EgressPlan
|
from ...egress import EgressPlan
|
||||||
from ...supervise import SupervisePlan
|
from ...supervise import SupervisePlan
|
||||||
from ...git_gate import GitGatePlan
|
from ...git_gate import GitGatePlan
|
||||||
|
|
||||||
# from ...workspace import workspace_plan as resolve_workspace_plan
|
|
||||||
from .bottle_plan import SmolmachinesBottlePlan
|
from .bottle_plan import SmolmachinesBottlePlan
|
||||||
from .util import smolmachines_bundle_subnet, smolmachines_preflight
|
from .util import smolmachines_bundle_subnet, smolmachines_preflight
|
||||||
|
|
||||||
@@ -79,5 +77,4 @@ def resolve_plan(
|
|||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
agent_provision=agent_provision_plan,
|
agent_provision=agent_provision_plan,
|
||||||
# workspace_plan=workspace_plan,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from . import tui
|
|||||||
def cmd_start(argv: list[str]) -> int:
|
def cmd_start(argv: list[str]) -> int:
|
||||||
parser = argparse.ArgumentParser(prog=f"{PROG} start", add_help=True)
|
parser = argparse.ArgumentParser(prog=f"{PROG} start", add_help=True)
|
||||||
parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
parser.add_argument("--cwd", action="store_true", help="copy host cwd into a derived image")
|
parser.add_argument("--cwd", action="store_true", help="copy host cwd into the running bottle")
|
||||||
parser.add_argument("--remote-control", action="store_true")
|
parser.add_argument("--remote-control", action="store_true")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--backend",
|
"--backend",
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
"""Unit: runtime workspace provisioning.
|
||||||
|
|
||||||
|
Workspace copy is intentionally handled through
|
||||||
|
`BottleBackend.provision_workspace` against a running bottle. The
|
||||||
|
Docker derived-image workspace path stays disabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
|
from bot_bottle import bottle_state
|
||||||
|
from bot_bottle import supervise
|
||||||
|
from bot_bottle.backend import Bottle, BottleSpec, ExecResult
|
||||||
|
from bot_bottle.backend.docker import DockerBottleBackend
|
||||||
|
from bot_bottle.backend.smolmachines import SmolmachinesBottleBackend
|
||||||
|
from bot_bottle.manifest import Manifest
|
||||||
|
|
||||||
|
|
||||||
|
def _manifest() -> Manifest:
|
||||||
|
return Manifest.from_json_obj({
|
||||||
|
"bottles": {"dev": {}},
|
||||||
|
"agents": {
|
||||||
|
"demo": {
|
||||||
|
"bottle": "dev",
|
||||||
|
"skills": [],
|
||||||
|
"prompt": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _spec(tmp: Path, *, copy_cwd: bool = True, identity: str = "demo-work") -> BottleSpec:
|
||||||
|
return BottleSpec(
|
||||||
|
manifest=_manifest(),
|
||||||
|
agent_name="demo",
|
||||||
|
copy_cwd=copy_cwd,
|
||||||
|
user_cwd=str(tmp),
|
||||||
|
identity=identity,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _bottle() -> MagicMock:
|
||||||
|
bottle = MagicMock(spec=Bottle)
|
||||||
|
bottle.name = "bot-bottle-demo-work"
|
||||||
|
bottle.exec.return_value = ExecResult(returncode=0, stdout="", stderr="")
|
||||||
|
return bottle
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeStateMixin:
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.tmp_dir = tempfile.TemporaryDirectory(prefix="backend-workspace.")
|
||||||
|
self.tmp = Path(self.tmp_dir.name)
|
||||||
|
self.root = self.tmp / ".bot-bottle"
|
||||||
|
self.original_root = supervise.bot_bottle_root
|
||||||
|
supervise.bot_bottle_root = lambda: self.root # type: ignore[assignment]
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
supervise.bot_bottle_root = self.original_root # type: ignore[assignment]
|
||||||
|
self.tmp_dir.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRuntimeWorkspaceProvisioning(_FakeStateMixin, unittest.TestCase):
|
||||||
|
def test_default_backend_method_copies_workspace_to_running_bottle(self) -> None:
|
||||||
|
(self.tmp / "src.txt").write_text("hello\n")
|
||||||
|
(self.tmp / ".git").mkdir()
|
||||||
|
backend = DockerBottleBackend()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("bot_bottle.backend.docker.resolve_plan.docker_mod.require_docker"),
|
||||||
|
patch(
|
||||||
|
"bot_bottle.backend.docker.resolve_plan.docker_mod.runsc_available",
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
plan = backend.prepare(_spec(self.tmp), self.tmp / "stage")
|
||||||
|
|
||||||
|
bottle = _bottle()
|
||||||
|
backend.provision_workspace(plan, bottle)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
"rm -rf /home/node/workspace && mkdir -p /home/node",
|
||||||
|
user="root",
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"chown -R node:node /home/node/workspace && "
|
||||||
|
"chmod 755 /home/node/workspace",
|
||||||
|
user="root",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottle.exec.call_args_list,
|
||||||
|
)
|
||||||
|
bottle.cp_in.assert_called_once_with(str(self.tmp), "/home/node/workspace")
|
||||||
|
|
||||||
|
def test_default_backend_method_noops_without_copy_cwd(self) -> None:
|
||||||
|
backend = DockerBottleBackend()
|
||||||
|
with (
|
||||||
|
patch("bot_bottle.backend.docker.resolve_plan.docker_mod.require_docker"),
|
||||||
|
patch(
|
||||||
|
"bot_bottle.backend.docker.resolve_plan.docker_mod.runsc_available",
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
plan = backend.prepare(_spec(self.tmp, copy_cwd=False), self.tmp / "stage")
|
||||||
|
|
||||||
|
bottle = _bottle()
|
||||||
|
backend.provision_workspace(plan, bottle)
|
||||||
|
|
||||||
|
bottle.exec.assert_not_called()
|
||||||
|
bottle.cp_in.assert_not_called()
|
||||||
|
|
||||||
|
def test_smolmachines_uses_same_running_bottle_method(self) -> None:
|
||||||
|
backend = SmolmachinesBottleBackend()
|
||||||
|
with patch(
|
||||||
|
"bot_bottle.backend.smolmachines.resolve_plan.smolmachines_preflight",
|
||||||
|
):
|
||||||
|
plan = backend.prepare(
|
||||||
|
_spec(self.tmp, identity="demo-smol-work"),
|
||||||
|
self.tmp / "stage",
|
||||||
|
)
|
||||||
|
|
||||||
|
bottle = _bottle()
|
||||||
|
backend.provision_workspace(plan, bottle)
|
||||||
|
|
||||||
|
bottle.cp_in.assert_called_once_with(str(self.tmp), "/home/node/workspace")
|
||||||
|
metadata = bottle_state.read_metadata("demo-smol-work")
|
||||||
|
self.assertIsNotNone(metadata)
|
||||||
|
self.assertEqual("smolmachines", metadata.backend)
|
||||||
|
|
||||||
|
|
||||||
|
class TestWorkspaceTrustPath(_FakeStateMixin, unittest.TestCase):
|
||||||
|
def test_prepare_trusts_workspace_path_when_copying_cwd(self) -> None:
|
||||||
|
backend = DockerBottleBackend()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("bot_bottle.backend.docker.resolve_plan.docker_mod.require_docker"),
|
||||||
|
patch(
|
||||||
|
"bot_bottle.backend.docker.resolve_plan.docker_mod.runsc_available",
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
plan = backend.prepare(_spec(self.tmp), self.tmp / "stage")
|
||||||
|
|
||||||
|
claude_config = self.root / "state" / "demo-work" / "agent" / "claude.json"
|
||||||
|
config = claude_config.read_text()
|
||||||
|
self.assertIn('"/home/node/workspace"', config)
|
||||||
|
self.assertEqual("/home/node/workspace", plan.workspace_plan.workdir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -33,7 +33,6 @@ from bot_bottle.egress import (
|
|||||||
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
SLUG = "demo-abc12"
|
SLUG = "demo-abc12"
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from bot_bottle.egress import EgressPlan
|
|||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
_URL = "http://supervise:9100/"
|
_URL = "http://supervise:9100/"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from bot_bottle.egress import EgressPlan
|
|||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
_URL = "http://supervise:9100/"
|
_URL = "http://supervise:9100/"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
def _manifest() -> Manifest:
|
def _manifest() -> Manifest:
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
class _Provider(AgentProvider):
|
class _Provider(AgentProvider):
|
||||||
@@ -124,10 +123,6 @@ class TestProvisionGitUser(unittest.TestCase):
|
|||||||
_PROVIDER.provision_git(bottle, _plan(stage_dir=self.stage))
|
_PROVIDER.provision_git(bottle, _plan(stage_dir=self.stage))
|
||||||
self.assertEqual([], _git_config_exec_calls(bottle))
|
self.assertEqual([], _git_config_exec_calls(bottle))
|
||||||
|
|
||||||
# def test_copies_cwd_git_to_workspace_plan_path(self):
|
|
||||||
# # DISABLED — workspace planning is currently commented out.
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def test_sets_name_and_email(self):
|
def test_sets_name_and_email(self):
|
||||||
plan = _plan(
|
plan = _plan(
|
||||||
git_user={"name": "Eric Bauerfeld", "email": "eric@dideric.is"},
|
git_user={"name": "Eric Bauerfeld", "email": "eric@dideric.is"},
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from bot_bottle.backend.smolmachines.bottle_plan import SmolmachinesBottlePlan
|
|||||||
from bot_bottle.egress import EgressPlan, EgressRoute
|
from bot_bottle.egress import EgressPlan, EgressRoute
|
||||||
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
||||||
from bot_bottle.manifest import Manifest
|
from bot_bottle.manifest import Manifest
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
def _manifest() -> Manifest:
|
def _manifest() -> Manifest:
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ from bot_bottle.egress import EgressPlan, EgressRoute
|
|||||||
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
||||||
from bot_bottle.manifest import ManifestGitEntry, Manifest
|
from bot_bottle.manifest import ManifestGitEntry, Manifest
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
# from bot_bottle.workspace import workspace_plan
|
|
||||||
|
|
||||||
|
|
||||||
class _Provider(AgentProvider):
|
class _Provider(AgentProvider):
|
||||||
@@ -337,9 +336,7 @@ class TestSmolmachinesBottleExec(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestProvisionGit(unittest.TestCase):
|
class TestProvisionGit(unittest.TestCase):
|
||||||
"""provision_git dispatches two independent passes (cwd .git
|
"""provision_git writes gitconfig insteadOf rules when configured."""
|
||||||
copy + gitconfig insteadOf write); each no-ops on its own
|
|
||||||
when its condition doesn't hold."""
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._tmp = tempfile.TemporaryDirectory(prefix="cb-prov-git.") # pylint: disable=consider-using-with
|
self._tmp = tempfile.TemporaryDirectory(prefix="cb-prov-git.") # pylint: disable=consider-using-with
|
||||||
@@ -354,14 +351,6 @@ class TestProvisionGit(unittest.TestCase):
|
|||||||
bottle.cp_in.assert_not_called()
|
bottle.cp_in.assert_not_called()
|
||||||
bottle.exec.assert_not_called()
|
bottle.exec.assert_not_called()
|
||||||
|
|
||||||
# def test_copies_cwd_git_when_copy_cwd_and_git_present(self):
|
|
||||||
# # DISABLED — workspace planning is currently commented out.
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# def test_skips_cwd_when_copy_cwd_false(self):
|
|
||||||
# # DISABLED — workspace planning is currently commented out.
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def test_writes_gitconfig_with_ip_port_form_for_smolmachines(self):
|
def test_writes_gitconfig_with_ip_port_form_for_smolmachines(self):
|
||||||
# Smolmachines's TSI-allowlisted guest dials git-gate via
|
# Smolmachines's TSI-allowlisted guest dials git-gate via
|
||||||
# smart HTTP at `127.0.0.1:<host port>` — the bundle's
|
# smart HTTP at `127.0.0.1:<host port>` — the bundle's
|
||||||
@@ -481,10 +470,5 @@ class TestProvisionGitUser(unittest.TestCase):
|
|||||||
self.assertIn("bot@example.com", calls[0][0])
|
self.assertIn("bot@example.com", calls[0][0])
|
||||||
|
|
||||||
|
|
||||||
# class TestProvisionWorkspace(unittest.TestCase):
|
|
||||||
# # DISABLED — workspace planning / provision_workspace are commented out.
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user