generated from coulomb/repo-seed
Add vm-packer build mode, profile.vm-packer-build, State Hub progress notes during long provision, docs/runbook, and build mode tests.
149 lines
4.3 KiB
Python
149 lines
4.3 KiB
Python
"""VMPackerExtension unit tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from subprocess import CompletedProcess
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from sandboxer.extensions.vm_packer import VMPackerExtension
|
|
from sandboxer.models import Profile
|
|
|
|
|
|
def _profile() -> Profile:
|
|
return Profile.model_validate(
|
|
{
|
|
"id": "profile.vm-haskell-build",
|
|
"version": "1.0.0",
|
|
"extension": "ext.vm-packer",
|
|
}
|
|
)
|
|
|
|
|
|
def _build_profile() -> Profile:
|
|
return Profile.model_validate(
|
|
{
|
|
"id": "profile.vm-packer-build",
|
|
"version": "1.0.0",
|
|
"extension": "ext.vm-packer",
|
|
}
|
|
)
|
|
|
|
|
|
def test_provision_attach_via_alias(tmp_path: Path) -> None:
|
|
repo = tmp_path / "proj"
|
|
repo.mkdir()
|
|
ext = VMPackerExtension()
|
|
|
|
with (
|
|
patch("sandboxer.extensions.vm_packer.SSHConfig.run", return_value=(0, "")),
|
|
patch("sandboxer.extensions.vm_packer.SSHConfig.rsync") as rsync,
|
|
):
|
|
handle = ext.provision(
|
|
_profile(),
|
|
{"vm": "haskell-build", "repo": str(repo)},
|
|
"localhost",
|
|
)
|
|
|
|
assert handle["vm_target"] == "haskell-build"
|
|
assert handle["remote_dir"].startswith("/build/sbx-")
|
|
rsync.assert_called_once()
|
|
|
|
|
|
def test_provision_requires_vm_input() -> None:
|
|
ext = VMPackerExtension()
|
|
with pytest.raises(ValueError, match="inputs.vm"):
|
|
ext.provision(_profile(), {}, "localhost")
|
|
|
|
|
|
def test_wait_ready_success() -> None:
|
|
ext = VMPackerExtension()
|
|
handle = {
|
|
"vm_host": "haskell-build",
|
|
"remote_dir": "/build/sbx-abc12345",
|
|
"host": "localhost",
|
|
"ssh_user": "build",
|
|
"ssh_port": "",
|
|
}
|
|
with patch("sandboxer.extensions.vm_packer.SSHConfig.run", return_value=(0, "ready\n")):
|
|
reach = ext.wait_ready(handle)
|
|
assert reach["remote_dir"] == "/build/sbx-abc12345"
|
|
assert "haskell-build" in (reach["ssh"] or "")
|
|
|
|
|
|
def test_teardown_preserves_vm() -> None:
|
|
ext = VMPackerExtension()
|
|
handle = {
|
|
"vm_host": "localhost",
|
|
"remote_dir": "/build/sbx-deadbeef",
|
|
"ssh_user": "build",
|
|
"ssh_port": "12222",
|
|
}
|
|
with patch("sandboxer.extensions.vm_packer.SSHConfig.run", return_value=(0, "")):
|
|
report = ext.teardown(handle)
|
|
assert report["vm_preserved"] == "true"
|
|
assert report["workspace_removed"] == "True"
|
|
|
|
|
|
def test_provision_build_runs_packer(tmp_path: Path) -> None:
|
|
template = tmp_path / "haskell"
|
|
template.mkdir()
|
|
(template / "haskell-build.pkr.hcl").write_text('packer {}')
|
|
ova = template / "haskell-build-20260624.ova"
|
|
ova.write_bytes(b"ova")
|
|
|
|
ext = VMPackerExtension()
|
|
with (
|
|
patch(
|
|
"sandboxer.extensions.vm_packer.subprocess.run",
|
|
return_value=CompletedProcess(args=[], returncode=0, stdout="", stderr=""),
|
|
) as run,
|
|
patch("sandboxer.extensions.vm_packer.emit_progress_note"),
|
|
):
|
|
handle = ext.provision(
|
|
_build_profile(),
|
|
{"vm_name": "haskell-build", "packer_template": str(template)},
|
|
"localhost",
|
|
)
|
|
|
|
assert handle["mode"] == "build"
|
|
assert handle["artifact_path"] == str(ova)
|
|
assert run.call_count == 2
|
|
init_cmd, build_cmd = [c.args[0] for c in run.call_args_list]
|
|
assert init_cmd[:2] == ["packer", "init"]
|
|
assert build_cmd[:2] == ["packer", "build"]
|
|
assert "-var" in build_cmd and "vm_name=haskell-build" in build_cmd
|
|
|
|
|
|
def test_provision_build_requires_template() -> None:
|
|
ext = VMPackerExtension()
|
|
with pytest.raises(ValueError, match="packer_template"):
|
|
ext.provision(_build_profile(), {"vm_name": "haskell-build"}, "localhost")
|
|
|
|
|
|
def test_wait_ready_build_checks_artifact(tmp_path: Path) -> None:
|
|
ova = tmp_path / "haskell-build.ova"
|
|
ova.write_bytes(b"ova")
|
|
ext = VMPackerExtension()
|
|
reach = ext.wait_ready(
|
|
{
|
|
"mode": "build",
|
|
"artifact_path": str(ova),
|
|
"host": "localhost",
|
|
}
|
|
)
|
|
assert reach["endpoint"] == str(ova)
|
|
|
|
|
|
def test_teardown_build_preserves_artifact() -> None:
|
|
ext = VMPackerExtension()
|
|
report = ext.teardown(
|
|
{
|
|
"mode": "build",
|
|
"artifact_path": "/tmp/haskell-build.ova",
|
|
}
|
|
)
|
|
assert report["artifact_preserved"] == "true"
|
|
assert report["workspace_removed"] == "false" |