feat(workspace): port cwd across backends
This commit is contained in:
@@ -25,6 +25,8 @@ from bot_bottle.workspace import workspace_plan
|
||||
|
||||
|
||||
def _plan(*, git_user: dict | None = None,
|
||||
copy_cwd: bool = False,
|
||||
user_cwd: str = "/tmp/x",
|
||||
stage_dir: Path | None = None) -> DockerBottlePlan:
|
||||
bottle_json: dict = {}
|
||||
if git_user is not None:
|
||||
@@ -35,7 +37,7 @@ def _plan(*, git_user: dict | None = None,
|
||||
})
|
||||
spec = BottleSpec(
|
||||
manifest=manifest, agent_name="demo",
|
||||
copy_cwd=False, user_cwd="/tmp/x",
|
||||
copy_cwd=copy_cwd, user_cwd=user_cwd,
|
||||
)
|
||||
return DockerBottlePlan(
|
||||
spec=spec,
|
||||
@@ -108,6 +110,28 @@ class TestProvisionGitUser(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual([], _git_config_calls(run))
|
||||
|
||||
def test_copies_cwd_git_to_workspace_plan_path(self):
|
||||
cwd = self.stage / "cwd"
|
||||
(cwd / ".git").mkdir(parents=True)
|
||||
plan = _plan(copy_cwd=True, user_cwd=str(cwd), stage_dir=self.stage)
|
||||
with patch.object(_git.subprocess, "run") as run:
|
||||
_git._provision_cwd_git(plan, "bot-bottle-demo-abc12")
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
"docker", "cp", f"{cwd}/.git",
|
||||
"bot-bottle-demo-abc12:/home/node/workspace/.git",
|
||||
],
|
||||
run.call_args_list[0].args[0],
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
"docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
|
||||
"chown", "-R", "node:node", "/home/node/workspace/.git",
|
||||
],
|
||||
run.call_args_list[1].args[0],
|
||||
)
|
||||
|
||||
def test_sets_name_and_email(self):
|
||||
plan = _plan(
|
||||
git_user={"name": "Eric Bauerfeld", "email": "eric@dideric.is"},
|
||||
|
||||
@@ -8,10 +8,13 @@ integration smoke."""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle.backend.docker import util as docker_mod
|
||||
from bot_bottle.workspace import WorkspacePlan
|
||||
|
||||
|
||||
def _ok(stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess:
|
||||
@@ -67,5 +70,26 @@ class TestSave(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestBuildImageWithCwd(unittest.TestCase):
|
||||
def test_uses_workspace_plan_paths(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-docker-cwd.") as tmp:
|
||||
workspace = WorkspacePlan(
|
||||
enabled=True,
|
||||
host_path=Path(tmp),
|
||||
guest_home="/guest/home",
|
||||
guest_path="/guest/home/workspace",
|
||||
workdir="/guest/home/workspace",
|
||||
)
|
||||
with patch.object(docker_mod.subprocess, "run") as run:
|
||||
docker_mod.build_image_with_cwd("derived:tag", "base:tag", workspace)
|
||||
|
||||
argv = run.call_args.args[0]
|
||||
dockerfile = run.call_args.kwargs["input"]
|
||||
self.assertEqual(["docker", "build", "-t", "derived:tag", "-f", "-", tmp], argv)
|
||||
self.assertIn("FROM base:tag\n", dockerfile)
|
||||
self.assertIn("COPY --chown=node:node . /guest/home/workspace\n", dockerfile)
|
||||
self.assertIn("WORKDIR /guest/home/workspace\n", dockerfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -30,6 +30,7 @@ from bot_bottle.backend.smolmachines.provision import (
|
||||
provider_auth as _provider_auth,
|
||||
skills as _skills,
|
||||
supervise as _supervise,
|
||||
workspace as _workspace,
|
||||
)
|
||||
from bot_bottle.backend.smolmachines.launch import _bundle_launch_spec
|
||||
from bot_bottle.backend.smolmachines.smolvm import SmolvmRunResult
|
||||
@@ -848,6 +849,55 @@ class TestProvisionGitUser(unittest.TestCase):
|
||||
self.assertEqual(["user.email", "bot@example.com"], calls[0][0][7:])
|
||||
|
||||
|
||||
class TestProvisionWorkspace(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="cb-prov-workspace.")
|
||||
self.stage = Path(self._tmp.name)
|
||||
|
||||
def tearDown(self):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_noop_when_copy_cwd_false(self):
|
||||
plan = _plan(copy_cwd=False, stage_dir=self.stage)
|
||||
with patch(
|
||||
"bot_bottle.backend.smolmachines.provision.workspace._smolvm.machine_cp"
|
||||
) as cp, patch(
|
||||
"bot_bottle.backend.smolmachines.provision.workspace._smolvm.machine_exec"
|
||||
) as ex:
|
||||
_workspace.provision_workspace(plan, "bot-bottle-demo-abc12")
|
||||
cp.assert_not_called()
|
||||
ex.assert_not_called()
|
||||
|
||||
def test_copies_workspace_to_plan_path_and_chowns(self):
|
||||
cwd = self.stage / "cwd"
|
||||
cwd.mkdir()
|
||||
plan = _plan(copy_cwd=True, user_cwd=str(cwd), stage_dir=self.stage)
|
||||
with patch(
|
||||
"bot_bottle.backend.smolmachines.provision.workspace._smolvm.machine_cp"
|
||||
) as cp, patch(
|
||||
"bot_bottle.backend.smolmachines.provision.workspace._smolvm.machine_exec"
|
||||
) as ex:
|
||||
_workspace.provision_workspace(plan, "bot-bottle-demo-abc12")
|
||||
|
||||
cp.assert_called_once_with(
|
||||
str(cwd),
|
||||
"bot-bottle-demo-abc12:/home/node/workspace",
|
||||
)
|
||||
argvs = [c.args[1] for c in ex.call_args_list]
|
||||
self.assertIn(
|
||||
["sh", "-c", "rm -rf /home/node/workspace && mkdir -p /home/node"],
|
||||
argvs,
|
||||
)
|
||||
self.assertIn(
|
||||
[
|
||||
"sh", "-c",
|
||||
"chown -R node:node /home/node/workspace && "
|
||||
"chmod 755 /home/node/workspace",
|
||||
],
|
||||
argvs,
|
||||
)
|
||||
|
||||
|
||||
class TestProvisionSupervise(unittest.TestCase):
|
||||
def test_noop_when_supervise_not_enabled(self):
|
||||
with patch(
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Unit: backend-neutral workspace planning."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle.backend import BottleSpec
|
||||
from bot_bottle.manifest import Manifest
|
||||
from bot_bottle.workspace import workspace_plan
|
||||
|
||||
|
||||
def _spec(*, copy_cwd: bool, user_cwd: str) -> BottleSpec:
|
||||
manifest = Manifest.from_json_obj({
|
||||
"bottles": {"dev": {}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
})
|
||||
return BottleSpec(
|
||||
manifest=manifest,
|
||||
agent_name="demo",
|
||||
copy_cwd=copy_cwd,
|
||||
user_cwd=user_cwd,
|
||||
)
|
||||
|
||||
|
||||
class TestWorkspacePlan(unittest.TestCase):
|
||||
def test_disabled_uses_guest_home_as_workdir(self):
|
||||
plan = workspace_plan(
|
||||
_spec(copy_cwd=False, user_cwd="/tmp/project"),
|
||||
guest_home="/home/node",
|
||||
)
|
||||
self.assertFalse(plan.enabled)
|
||||
self.assertEqual("/home/node", plan.guest_path)
|
||||
self.assertEqual("/home/node", plan.workdir)
|
||||
|
||||
def test_enabled_uses_workspace_under_guest_home(self):
|
||||
plan = workspace_plan(
|
||||
_spec(copy_cwd=True, user_cwd="/tmp/project"),
|
||||
guest_home="/guest/home",
|
||||
)
|
||||
self.assertTrue(plan.enabled)
|
||||
self.assertEqual(Path("/tmp/project"), plan.host_path)
|
||||
self.assertEqual("/guest/home/workspace", plan.guest_path)
|
||||
self.assertEqual("/guest/home/workspace", plan.workdir)
|
||||
|
||||
def test_detects_host_git_dir(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-workspace.") as tmp:
|
||||
Path(tmp, ".git").mkdir()
|
||||
plan = workspace_plan(
|
||||
_spec(copy_cwd=True, user_cwd=tmp),
|
||||
guest_home="/home/node",
|
||||
)
|
||||
self.assertTrue(plan.has_host_git_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user