fix(commit): stop running macos-container bottle before committing
`container export` requires the container to be stopped first. When a running bottle is detected, prompt the user to confirm, stop the container, then commit. Adds `container_is_running` and `stop_container` helpers to the macos-container util. Addresses #240 (comment)
This commit is contained in:
@@ -252,6 +252,36 @@ def container_exists(name: str) -> bool:
|
||||
return name in {line.strip() for line in result.stdout.splitlines()}
|
||||
|
||||
|
||||
def container_is_running(name: str) -> bool:
|
||||
"""Return True if the named container is currently running.
|
||||
|
||||
`container list` without `--all` lists only running containers."""
|
||||
result = subprocess.run(
|
||||
[_CONTAINER, "list", "--quiet"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return False
|
||||
return name in {line.strip() for line in result.stdout.splitlines()}
|
||||
|
||||
|
||||
def stop_container(name: str) -> None:
|
||||
"""Stop the named container without deleting it."""
|
||||
result = subprocess.run(
|
||||
[_CONTAINER, "stop", name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
die(
|
||||
f"container stop {name!r} failed: "
|
||||
f"{(result.stderr or '').strip() or '<no stderr>'}"
|
||||
)
|
||||
|
||||
|
||||
def force_remove_container(name: str) -> None:
|
||||
if container_exists(name):
|
||||
subprocess.run(
|
||||
|
||||
@@ -11,16 +11,19 @@ snapshot instead of rebuilding from the Dockerfile.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from ..backend import enumerate_active_agents
|
||||
from ..backend.docker.util import commit_container as docker_commit_container
|
||||
from ..backend.macos_container.util import commit_container as macos_commit_container
|
||||
from ..backend.macos_container.util import container_is_running as macos_container_is_running
|
||||
from ..backend.macos_container.util import stop_container as macos_stop_container
|
||||
from ..backend.smolmachines.smolvm import pack_create_from_vm
|
||||
from ..bottle_state import bottle_state_dir
|
||||
from ..bottle_state import mark_preserved, read_metadata, write_committed_image
|
||||
from ..log import die, info
|
||||
from ._common import PROG
|
||||
from ._common import PROG, read_tty_line
|
||||
from . import tui
|
||||
|
||||
|
||||
@@ -91,6 +94,17 @@ def cmd_commit(argv: list[str]) -> int:
|
||||
container = _agent_container_name(slug)
|
||||
image_tag = _committed_image_tag(slug)
|
||||
|
||||
if macos_container_is_running(container):
|
||||
sys.stderr.write(
|
||||
f"bot-bottle: bottle {slug!r} is running; "
|
||||
"commit will stop it. Continue? [y/N] "
|
||||
)
|
||||
sys.stderr.flush()
|
||||
reply = read_tty_line().strip().lower()
|
||||
if reply not in ("y", "yes"):
|
||||
return 0
|
||||
macos_stop_container(container)
|
||||
|
||||
macos_commit_container(container, image_tag)
|
||||
write_committed_image(slug, image_tag)
|
||||
mark_preserved(slug)
|
||||
|
||||
@@ -143,6 +143,8 @@ class TestCmdCommitSlugArg(_FakeHomeMixin, unittest.TestCase):
|
||||
"bot_bottle.cli.commit.macos_commit_container",
|
||||
) as mock_commit, patch(
|
||||
"bot_bottle.cli.commit.info",
|
||||
), patch(
|
||||
"bot_bottle.cli.commit.macos_container_is_running", return_value=False,
|
||||
):
|
||||
rc = cmd_commit([slug])
|
||||
|
||||
@@ -152,6 +154,57 @@ class TestCmdCommitSlugArg(_FakeHomeMixin, unittest.TestCase):
|
||||
f"bot-bottle-committed-{slug}:latest",
|
||||
)
|
||||
|
||||
def test_running_macos_container_stops_then_commits_on_yes(self):
|
||||
slug = "dev-abc12"
|
||||
bottle_state.write_metadata(bottle_state.BottleMetadata(
|
||||
identity=slug, agent_name="dev", cwd="", copy_cwd=False,
|
||||
started_at="t", backend="macos-container",
|
||||
))
|
||||
|
||||
with patch(
|
||||
"bot_bottle.cli.commit.macos_container_is_running", return_value=True,
|
||||
), patch(
|
||||
"bot_bottle.cli.commit.read_tty_line", return_value="y",
|
||||
), patch(
|
||||
"bot_bottle.cli.commit.macos_stop_container",
|
||||
) as mock_stop, patch(
|
||||
"bot_bottle.cli.commit.macos_commit_container",
|
||||
) as mock_commit, patch(
|
||||
"bot_bottle.cli.commit.info",
|
||||
):
|
||||
rc = cmd_commit([slug])
|
||||
|
||||
self.assertEqual(0, rc)
|
||||
mock_stop.assert_called_once_with(f"bot-bottle-{slug}")
|
||||
mock_commit.assert_called_once_with(
|
||||
f"bot-bottle-{slug}",
|
||||
f"bot-bottle-committed-{slug}:latest",
|
||||
)
|
||||
|
||||
def test_running_macos_container_aborts_on_no(self):
|
||||
slug = "dev-abc12"
|
||||
bottle_state.write_metadata(bottle_state.BottleMetadata(
|
||||
identity=slug, agent_name="dev", cwd="", copy_cwd=False,
|
||||
started_at="t", backend="macos-container",
|
||||
))
|
||||
|
||||
with patch(
|
||||
"bot_bottle.cli.commit.macos_container_is_running", return_value=True,
|
||||
), patch(
|
||||
"bot_bottle.cli.commit.read_tty_line", return_value="n",
|
||||
), patch(
|
||||
"bot_bottle.cli.commit.macos_stop_container",
|
||||
) as mock_stop, patch(
|
||||
"bot_bottle.cli.commit.macos_commit_container",
|
||||
) as mock_commit, patch(
|
||||
"bot_bottle.cli.commit.info",
|
||||
):
|
||||
rc = cmd_commit([slug])
|
||||
|
||||
self.assertEqual(0, rc)
|
||||
mock_stop.assert_not_called()
|
||||
mock_commit.assert_not_called()
|
||||
|
||||
|
||||
class TestCmdCommitSmolmachinesBackend(_FakeHomeMixin, unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user