fix(smolmachines): reset codex runtime db before auth check
This commit is contained in:
@@ -22,10 +22,50 @@ def provision_provider_auth(plan: SmolmachinesBottlePlan, target: str) -> None:
|
|||||||
if not plan.codex_auth_file:
|
if not plan.codex_auth_file:
|
||||||
return
|
return
|
||||||
guest_home = os.environ.get("BOT_BOTTLE_GUEST_HOME", _DEFAULT_GUEST_HOME)
|
guest_home = os.environ.get("BOT_BOTTLE_GUEST_HOME", _DEFAULT_GUEST_HOME)
|
||||||
auth_dir = f"{guest_home}/.codex"
|
auth_dir = plan.guest_env.get("CODEX_HOME", f"{guest_home}/.codex")
|
||||||
auth_path = f"{auth_dir}/auth.json"
|
|
||||||
|
|
||||||
_smolvm.machine_exec(target, ["mkdir", "-p", auth_dir])
|
result = _smolvm.machine_exec(
|
||||||
|
target,
|
||||||
|
["mkdir", "-p", auth_dir],
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
detail = (result.stderr or result.stdout).strip()
|
||||||
|
if detail:
|
||||||
|
detail = f": {detail}"
|
||||||
|
die(f"codex host credentials: could not create {auth_dir}{detail}")
|
||||||
|
result = _smolvm.machine_exec(target, ["chown", "node:node", auth_dir])
|
||||||
|
if result.returncode != 0:
|
||||||
|
detail = (result.stderr or result.stdout).strip()
|
||||||
|
if detail:
|
||||||
|
detail = f": {detail}"
|
||||||
|
die(f"codex host credentials: could not chown {auth_dir}{detail}")
|
||||||
|
result = _smolvm.machine_exec(target, ["chmod", "700", auth_dir])
|
||||||
|
if result.returncode != 0:
|
||||||
|
detail = (result.stderr or result.stdout).strip()
|
||||||
|
if detail:
|
||||||
|
detail = f": {detail}"
|
||||||
|
die(f"codex host credentials: could not chmod {auth_dir}{detail}")
|
||||||
|
result = _smolvm.machine_exec(
|
||||||
|
target,
|
||||||
|
[
|
||||||
|
"find", auth_dir,
|
||||||
|
"-maxdepth", "1",
|
||||||
|
"-type", "f",
|
||||||
|
"(",
|
||||||
|
"-name", "*.sqlite",
|
||||||
|
"-o", "-name", "*.sqlite-*",
|
||||||
|
"-o", "-name", "*.codex-repair-*.bak",
|
||||||
|
")",
|
||||||
|
"-delete",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
detail = (result.stderr or result.stdout).strip()
|
||||||
|
if detail:
|
||||||
|
detail = f": {detail}"
|
||||||
|
die(f"codex host credentials: could not reset runtime db files{detail}")
|
||||||
|
|
||||||
|
auth_path = f"{auth_dir}/auth.json"
|
||||||
_smolvm.machine_cp(str(plan.codex_auth_file), f"{target}:{auth_path}")
|
_smolvm.machine_cp(str(plan.codex_auth_file), f"{target}:{auth_path}")
|
||||||
_smolvm.machine_exec(target, ["chown", "node:node", auth_path])
|
_smolvm.machine_exec(target, ["chown", "node:node", auth_path])
|
||||||
_smolvm.machine_exec(target, ["chmod", "600", auth_path])
|
_smolvm.machine_exec(target, ["chmod", "600", auth_path])
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ def _plan(
|
|||||||
agent_git_gate_host: str = "127.0.0.1:55555",
|
agent_git_gate_host: str = "127.0.0.1:55555",
|
||||||
agent_supervise_url: str = "http://127.0.0.1:55556/",
|
agent_supervise_url: str = "http://127.0.0.1:55556/",
|
||||||
codex_auth_file: Path | None = None,
|
codex_auth_file: Path | None = None,
|
||||||
|
guest_env: dict[str, str] | None = None,
|
||||||
) -> SmolmachinesBottlePlan:
|
) -> SmolmachinesBottlePlan:
|
||||||
bottle_json: dict = {}
|
bottle_json: dict = {}
|
||||||
git_json: dict = {}
|
git_json: dict = {}
|
||||||
@@ -107,7 +108,7 @@ def _plan(
|
|||||||
bundle_ip=bundle_ip,
|
bundle_ip=bundle_ip,
|
||||||
machine_name="bot-bottle-demo-abc12",
|
machine_name="bot-bottle-demo-abc12",
|
||||||
agent_image_ref="bot-bottle-claude:latest",
|
agent_image_ref="bot-bottle-claude:latest",
|
||||||
guest_env={},
|
guest_env=dict(guest_env or {}),
|
||||||
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
|
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
|
||||||
proxy_plan=PipelockProxyPlan(
|
proxy_plan=PipelockProxyPlan(
|
||||||
yaml_path=Path("/tmp/pipelock.yaml"),
|
yaml_path=Path("/tmp/pipelock.yaml"),
|
||||||
@@ -193,24 +194,26 @@ class TestProvisionPrompt(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestProvisionProviderAuth(unittest.TestCase):
|
class TestProvisionProviderAuth(unittest.TestCase):
|
||||||
def test_noop_without_codex_auth_file(self):
|
def _patch(self):
|
||||||
with patch(
|
return (
|
||||||
|
patch(
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_cp"
|
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_cp"
|
||||||
) as cp, patch(
|
),
|
||||||
|
patch(
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_exec"
|
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_exec"
|
||||||
) as ex:
|
),
|
||||||
_provider_auth.provision_provider_auth(
|
|
||||||
_plan(), "bot-bottle-demo-abc12",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_noop_without_codex_auth_file(self):
|
||||||
|
cp_p, ex_p = self._patch()
|
||||||
|
with cp_p as cp, ex_p as ex:
|
||||||
|
_provider_auth.provision_provider_auth(_plan(), "bot-bottle-demo-abc12")
|
||||||
self.assertEqual(0, cp.call_count)
|
self.assertEqual(0, cp.call_count)
|
||||||
self.assertEqual(0, ex.call_count)
|
self.assertEqual(0, ex.call_count)
|
||||||
|
|
||||||
def test_copies_dummy_auth_json_to_codex_home(self):
|
def test_copies_dummy_auth_json_to_codex_home(self):
|
||||||
with patch(
|
cp_p, ex_p = self._patch()
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_cp"
|
with cp_p as cp, ex_p as ex:
|
||||||
) as cp, patch(
|
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_exec"
|
|
||||||
) as ex:
|
|
||||||
ex.return_value = SmolvmRunResult(0, "Logged in using ChatGPT\n", "")
|
ex.return_value = SmolvmRunResult(0, "Logged in using ChatGPT\n", "")
|
||||||
_provider_auth.provision_provider_auth(
|
_provider_auth.provision_provider_auth(
|
||||||
_plan(codex_auth_file=Path("/tmp/codex-auth.json")),
|
_plan(codex_auth_file=Path("/tmp/codex-auth.json")),
|
||||||
@@ -222,6 +225,28 @@ class TestProvisionProviderAuth(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
argv_seen = [call.args[1] for call in ex.call_args_list]
|
argv_seen = [call.args[1] for call in ex.call_args_list]
|
||||||
self.assertIn(["mkdir", "-p", "/home/node/.codex"], argv_seen)
|
self.assertIn(["mkdir", "-p", "/home/node/.codex"], argv_seen)
|
||||||
|
self.assertIn(
|
||||||
|
["chown", "node:node", "/home/node/.codex"],
|
||||||
|
argv_seen,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
["chmod", "700", "/home/node/.codex"],
|
||||||
|
argv_seen,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
[
|
||||||
|
"find", "/home/node/.codex",
|
||||||
|
"-maxdepth", "1",
|
||||||
|
"-type", "f",
|
||||||
|
"(",
|
||||||
|
"-name", "*.sqlite",
|
||||||
|
"-o", "-name", "*.sqlite-*",
|
||||||
|
"-o", "-name", "*.codex-repair-*.bak",
|
||||||
|
")",
|
||||||
|
"-delete",
|
||||||
|
],
|
||||||
|
argv_seen,
|
||||||
|
)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
["chown", "node:node", "/home/node/.codex/auth.json"],
|
["chown", "node:node", "/home/node/.codex/auth.json"],
|
||||||
argv_seen,
|
argv_seen,
|
||||||
@@ -238,16 +263,65 @@ class TestProvisionProviderAuth(unittest.TestCase):
|
|||||||
argv_seen,
|
argv_seen,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_dies_when_codex_rejects_dummy_auth(self):
|
def test_honors_codex_home_from_guest_env(self):
|
||||||
with patch(
|
cp_p, ex_p = self._patch()
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_cp"
|
with cp_p as cp, ex_p as ex:
|
||||||
), patch(
|
ex.return_value = SmolvmRunResult(0, "Logged in using ChatGPT\n", "")
|
||||||
"bot_bottle.backend.smolmachines.provision.provider_auth._smolvm.machine_exec"
|
_provider_auth.provision_provider_auth(
|
||||||
) as ex:
|
_plan(
|
||||||
ex.return_value = SmolvmRunResult(1, "Not logged in\n", "")
|
codex_auth_file=Path("/tmp/codex-auth.json"),
|
||||||
|
guest_env={"CODEX_HOME": "/run/codex-home"},
|
||||||
|
),
|
||||||
|
"bot-bottle-demo-abc12",
|
||||||
|
)
|
||||||
|
cp.assert_called_once_with(
|
||||||
|
"/tmp/codex-auth.json",
|
||||||
|
"bot-bottle-demo-abc12:/run/codex-home/auth.json",
|
||||||
|
)
|
||||||
|
argv_seen = [call.args[1] for call in ex.call_args_list]
|
||||||
|
self.assertIn(
|
||||||
|
[
|
||||||
|
"runuser", "-u", "node", "--",
|
||||||
|
"env",
|
||||||
|
"HOME=/home/node",
|
||||||
|
"CODEX_HOME=/run/codex-home",
|
||||||
|
"codex", "login", "status",
|
||||||
|
],
|
||||||
|
argv_seen,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_dies_when_codex_home_cannot_be_created(self):
|
||||||
|
cp_p, ex_p = self._patch()
|
||||||
|
with cp_p as cp, ex_p as ex:
|
||||||
|
ex.return_value = SmolvmRunResult(1, "", "mkdir: nope\n")
|
||||||
with self.assertRaises(SystemExit):
|
with self.assertRaises(SystemExit):
|
||||||
_provider_auth.provision_provider_auth(
|
_provider_auth.provision_provider_auth(
|
||||||
_plan(codex_auth_file=Path("/tmp/codex-auth.json")),
|
_plan(
|
||||||
|
codex_auth_file=Path("/tmp/codex-auth.json"),
|
||||||
|
),
|
||||||
|
"bot-bottle-demo-abc12",
|
||||||
|
)
|
||||||
|
self.assertEqual(0, cp.call_count)
|
||||||
|
self.assertEqual(1, ex.call_count)
|
||||||
|
|
||||||
|
def test_dies_when_codex_rejects_dummy_auth(self):
|
||||||
|
cp_p, ex_p = self._patch()
|
||||||
|
with cp_p, ex_p as ex:
|
||||||
|
# CODEX_HOME setup ok (0), but codex login status fails (1).
|
||||||
|
ex.side_effect = [
|
||||||
|
SmolvmRunResult(0, "", ""), # mkdir CODEX_HOME
|
||||||
|
SmolvmRunResult(0, "", ""), # chown CODEX_HOME
|
||||||
|
SmolvmRunResult(0, "", ""), # chmod CODEX_HOME
|
||||||
|
SmolvmRunResult(0, "", ""), # reset runtime db files
|
||||||
|
SmolvmRunResult(0, "", ""), # chown auth.json
|
||||||
|
SmolvmRunResult(0, "", ""), # chmod auth.json
|
||||||
|
SmolvmRunResult(1, "Not logged in\n", ""), # login status
|
||||||
|
]
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
_provider_auth.provision_provider_auth(
|
||||||
|
_plan(
|
||||||
|
codex_auth_file=Path("/tmp/codex-auth.json"),
|
||||||
|
),
|
||||||
"bot-bottle-demo-abc12",
|
"bot-bottle-demo-abc12",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user