Harden default environment pull behavior
Fix the default one-shot install path so empty bundled profile directories no longer win over OCI-backed environment pulls or leave broken cached symlinks behind. Treat cached installs as valid only when the manifest and boot artifacts are all present, repair invalid installs on the next pull, and add human-mode phase markers for env pull and run without changing JSON output. Align the Python lifecycle example and public docs with the current exec_vm/vm_exec auto-clean semantics, and validate the slice with focused pytest coverage, make check, make dist-check, and a real default-path pull/inspect/run smoke.
This commit is contained in:
parent
694be0730b
commit
6e16e74fd5
16 changed files with 384 additions and 91 deletions
|
|
@ -160,11 +160,21 @@ def test_cli_run_prints_human_output(
|
|||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def run_in_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
def create_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
assert kwargs["vcpu_count"] == 1
|
||||
assert kwargs["mem_mib"] == 1024
|
||||
return {"vm_id": "vm-123"}
|
||||
|
||||
def start_vm(self, vm_id: str) -> dict[str, Any]:
|
||||
assert vm_id == "vm-123"
|
||||
return {"vm_id": vm_id, "state": "started"}
|
||||
|
||||
def exec_vm(self, vm_id: str, *, command: str, timeout_seconds: int) -> dict[str, Any]:
|
||||
assert vm_id == "vm-123"
|
||||
assert command == "echo hi"
|
||||
assert timeout_seconds == 30
|
||||
return {
|
||||
"environment": kwargs["environment"],
|
||||
"environment": "debian:12",
|
||||
"execution_mode": "guest_vsock",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 12,
|
||||
|
|
@ -172,6 +182,10 @@ def test_cli_run_prints_human_output(
|
|||
"stderr": "",
|
||||
}
|
||||
|
||||
@property
|
||||
def manager(self) -> Any:
|
||||
raise AssertionError("manager cleanup should not be used on a successful run")
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
|
|
@ -194,6 +208,9 @@ def test_cli_run_prints_human_output(
|
|||
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == "hi\n"
|
||||
assert "[run] phase=create environment=debian:12" in captured.err
|
||||
assert "[run] phase=start vm_id=vm-123" in captured.err
|
||||
assert "[run] phase=execute vm_id=vm-123" in captured.err
|
||||
assert "[run] environment=debian:12 execution_mode=guest_vsock exit_code=0" in captured.err
|
||||
|
||||
|
||||
|
|
@ -202,8 +219,18 @@ def test_cli_run_exits_with_command_status(
|
|||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def run_in_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
def create_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
del kwargs
|
||||
return {"vm_id": "vm-456"}
|
||||
|
||||
def start_vm(self, vm_id: str) -> dict[str, Any]:
|
||||
assert vm_id == "vm-456"
|
||||
return {"vm_id": vm_id, "state": "started"}
|
||||
|
||||
def exec_vm(self, vm_id: str, *, command: str, timeout_seconds: int) -> dict[str, Any]:
|
||||
assert vm_id == "vm-456"
|
||||
assert command == "false"
|
||||
assert timeout_seconds == 30
|
||||
return {
|
||||
"environment": "debian:12",
|
||||
"execution_mode": "guest_vsock",
|
||||
|
|
@ -213,6 +240,10 @@ def test_cli_run_exits_with_command_status(
|
|||
"stderr": "bad\n",
|
||||
}
|
||||
|
||||
@property
|
||||
def manager(self) -> Any:
|
||||
raise AssertionError("manager cleanup should not be used when exec_vm returns normally")
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
|
|
@ -238,6 +269,50 @@ def test_cli_run_exits_with_command_status(
|
|||
assert "bad\n" in captured.err
|
||||
|
||||
|
||||
def test_cli_env_pull_prints_human_progress(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def pull_environment(self, environment: str) -> dict[str, Any]:
|
||||
assert environment == "debian:12"
|
||||
return {
|
||||
"name": "debian:12",
|
||||
"version": "1.0.0",
|
||||
"distribution": "debian",
|
||||
"distribution_version": "12",
|
||||
"installed": True,
|
||||
"cache_dir": "/tmp/cache",
|
||||
"default_packages": ["bash", "git"],
|
||||
"install_dir": "/tmp/cache/linux-x86_64/debian_12-1.0.0",
|
||||
"install_manifest": "/tmp/cache/linux-x86_64/debian_12-1.0.0/environment.json",
|
||||
"kernel_image": "/tmp/cache/linux-x86_64/debian_12-1.0.0/vmlinux",
|
||||
"rootfs_image": "/tmp/cache/linux-x86_64/debian_12-1.0.0/rootfs.ext4",
|
||||
"oci_registry": "registry-1.docker.io",
|
||||
"oci_repository": "thalesmaciel/pyro-environment-debian-12",
|
||||
"oci_reference": "1.0.0",
|
||||
}
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="env",
|
||||
env_command="pull",
|
||||
environment="debian:12",
|
||||
json=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
|
||||
cli.main()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "[pull] phase=install environment=debian:12" in captured.err
|
||||
assert "[pull] phase=ready environment=debian:12" in captured.err
|
||||
assert "Pulled: debian:12" in captured.out
|
||||
|
||||
|
||||
def test_cli_requires_run_command() -> None:
|
||||
with pytest.raises(ValueError, match="command is required"):
|
||||
cli._require_command([])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue