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:
Thales Maciel 2026-03-11 19:27:09 -03:00
parent 694be0730b
commit 6e16e74fd5
16 changed files with 384 additions and 91 deletions

View file

@ -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([])