From de5348c36c0ef86779cf7f99cc3fd40cb95cbf30 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 8 Jun 2026 00:32:13 +0200 Subject: [PATCH] refactor(cli): rename `vmgui` command to `vmware` Rename the GUI launcher command from `vmgui` to `vmware` (`ci vmware ...`). Updates the click command name, function, log-file prefix, __main__ registration, tests and runbook references. Behaviour unchanged. --- docs/RUNBOOK-PhaseC.md | 18 +++++------ src/ci_orchestrator/__main__.py | 4 +-- src/ci_orchestrator/commands/vm.py | 16 ++++----- tests/python/test_commands_vm.py | 52 +++++++++++++++--------------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/RUNBOOK-PhaseC.md b/docs/RUNBOOK-PhaseC.md index ff96f2f..85feda3 100644 --- a/docs/RUNBOOK-PhaseC.md +++ b/docs/RUNBOOK-PhaseC.md @@ -69,7 +69,7 @@ The script defines two helpers: | `ci artifacts collect ...` | copy artifacts out of a guest | §4 | | `ci vm remove --vmx ...` | stop + delete one clone | §4 | | `ci vm cleanup` | sweep orphaned clones + stale locks | §4 / §6 | -| `ci vmgui --vmx ...` | open a VMX in the VMware Workstation GUI | §4 | +| `ci vmware --vmx ...` | open a VMX in the VMware Workstation GUI | §4 | | `ci creds set --user ...` | store guest credentials in keyring | §5 | | `ci template deploy-linux` | build the Linux template VM | §5 | | `ci template prepare-linux` | provision Linux template over SSH | §5 | @@ -217,19 +217,19 @@ Launches the **interactive** VMware Workstation GUI (not the headless vmrun path so you can watch/poke a clone by hand. On a normal desktop session it just works — `sudo` already forwards `DISPLAY`, -and `vmgui` behaves exactly like `sudo -u ci-runner vmware`: +and `vmware` behaves exactly like `sudo -u ci-runner vmware`: ```bash -ci vmgui # bare GUI, no VM -ci vmgui --vmx /var/lib/ci/build-vms/Clone_dbg-1_*/Clone_dbg-1_*.vmx -ci vmgui --vmx --power-on # power it on (-x) -ci vmgui --vmx --fullscreen # power on + fullscreen (-X) +ci vmware # bare GUI, no VM +ci vmware --vmx /var/lib/ci/build-vms/Clone_dbg-1_*/Clone_dbg-1_*.vmx +ci vmware --vmx --power-on # power it on (-x) +ci vmware --vmx --fullscreen # power on + fullscreen (-X) ``` Other flags: `--new-window` (`-n`), `--display :0` (force a display if `$DISPLAY` isn't forwarded), `--vmware-path`. Spawns detached; prints PID + log path. **Why it works**: VMware connects via the X **host ACL**, not the cookie. The inherited `XAUTHORITY` (the desktop user's `~/.Xauthority`) is unreadable by -ci-runner, so `vmgui` drops it and relies on the xhost grant — which is normally +ci-runner, so `vmware` drops it and relies on the xhost grant — which is normally already present (`xhost` lists `SI:localuser:ci-runner`). **Only if you get `cannot open X display`**: the grant is missing. Add it from @@ -237,8 +237,8 @@ the logged-in desktop user (not ci-runner): ```bash xhost +SI:localuser:ci-runner ``` -`vmgui` pre-flights the display (`xdpyinfo`) and prints this exact command on -failure instead of dying silently; the GUI's output is captured in `/tmp/vmgui-*.log`. +`vmware` pre-flights the display (`xdpyinfo`) and prints this exact command on +failure instead of dying silently; the GUI's output is captured in `/tmp/vmware-*.log`. --- diff --git a/src/ci_orchestrator/__main__.py b/src/ci_orchestrator/__main__.py index fcd22da..b149674 100644 --- a/src/ci_orchestrator/__main__.py +++ b/src/ci_orchestrator/__main__.py @@ -23,7 +23,7 @@ from ci_orchestrator.commands.retention import retention from ci_orchestrator.commands.smoke import smoke from ci_orchestrator.commands.template import template from ci_orchestrator.commands.validate import validate -from ci_orchestrator.commands.vm import vm, vmgui +from ci_orchestrator.commands.vm import vm, vmware # Re-export so legacy A1 tests that patched ``cli_module.`` keep working. from ci_orchestrator.commands.wait import ( # noqa: F401 (intentional re-exports) @@ -54,7 +54,7 @@ cli.add_command(bench) cli.add_command(smoke) cli.add_command(validate) cli.add_command(creds) -cli.add_command(vmgui) +cli.add_command(vmware) if __name__ == "__main__": # pragma: no cover diff --git a/src/ci_orchestrator/commands/vm.py b/src/ci_orchestrator/commands/vm.py index 79a5a98..10eeecf 100644 --- a/src/ci_orchestrator/commands/vm.py +++ b/src/ci_orchestrator/commands/vm.py @@ -534,7 +534,7 @@ def _launch_gui(argv: list[str], env: dict[str, str], log_path: Path) -> subproc ) -@click.command("vmgui") +@click.command("vmware") @click.option( "--vmx", "--vm-path", @@ -575,7 +575,7 @@ def _launch_gui(argv: list[str], env: dict[str, str], log_path: Path) -> subproc default=None, help="X display to use (default: inherit $DISPLAY). e.g. ':0'.", ) -def vmgui( +def vmware( vmx: str | None, vmware_path: str, power_on: bool, @@ -619,7 +619,7 @@ def vmgui( if xauth and not os.access(xauth, os.R_OK): env.pop("XAUTHORITY", None) click.echo( - f"[vmgui] dropping unreadable XAUTHORITY ({xauth}); relying on xhost.", + f"[vmware] dropping unreadable XAUTHORITY ({xauth}); relying on xhost.", err=True, ) @@ -644,14 +644,14 @@ def vmgui( argv.append(str(vmx_path)) elif power_on or fullscreen: click.echo( - "[vmgui] note: --power-on/--fullscreen ignored without --vmx.", + "[vmware] note: --power-on/--fullscreen ignored without --vmx.", err=True, ) ts = datetime.now(tz=UTC).strftime("%Y%m%d-%H%M%S") - log_path = Path(tempfile.gettempdir()) / f"vmgui-{os.getpid()}-{ts}.log" + log_path = Path(tempfile.gettempdir()) / f"vmware-{os.getpid()}-{ts}.log" - click.echo(f"[vmgui] launching VMware GUI: {' '.join(argv)}") + click.echo(f"[vmware] launching VMware GUI: {' '.join(argv)}") try: proc = _launch_gui(argv, env, log_path) except (OSError, ValueError) as exc: @@ -672,8 +672,8 @@ def vmgui( ) click.echo( - f"[vmgui] VMware GUI started (pid {proc.pid}); display={disp}; log={log_path}" + f"[vmware] VMware GUI started (pid {proc.pid}); display={disp}; log={log_path}" ) -__all__ = ["cleanup_orphans", "vm", "vmgui"] +__all__ = ["cleanup_orphans", "vm", "vmware"] diff --git a/tests/python/test_commands_vm.py b/tests/python/test_commands_vm.py index 3164471..5d2a6c4 100644 --- a/tests/python/test_commands_vm.py +++ b/tests/python/test_commands_vm.py @@ -499,11 +499,11 @@ def test_vm_cleanup_backend_unavailable(monkeypatch: pytest.MonkeyPatch, tmp_pat assert "vmrun not available" in result.output -# ── vmgui ─────────────────────────────────────────────────────────────────── +# ── vmware ─────────────────────────────────────────────────────────────────── class _FakeProc: - """Minimal subprocess.Popen stand-in for vmgui tests.""" + """Minimal subprocess.Popen stand-in for vmware GUI tests.""" def __init__(self, pid: int = 4242, rc: int | None = None) -> None: self.pid = pid @@ -533,68 +533,68 @@ def _capture_launch( return captured -def test_vmgui_missing_vmx_errors(tmp_path: Path) -> None: - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(tmp_path / "nope.vmx")]) +def test_vmware_missing_vmx_errors(tmp_path: Path) -> None: + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(tmp_path / "nope.vmx")]) assert result.exit_code != 0 assert "VMX file not found" in result.output -def test_vmgui_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code == 0, result.output assert cap["argv"][-1] == str(vmx) assert "-x" not in cap["argv"] and "-X" not in cap["argv"] assert "pid 4242" in result.output -def test_vmgui_power_on_adds_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_power_on_adds_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx), "--power-on"]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx), "--power-on"]) assert result.exit_code == 0, result.output assert "-x" in cap["argv"] -def test_vmgui_fullscreen_adds_capital_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_fullscreen_adds_capital_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") result = CliRunner().invoke( - cli, ["vmgui", "--vmx", str(vmx), "--fullscreen", "--new-window"] + cli, ["vmware", "--vmx", str(vmx), "--fullscreen", "--new-window"] ) assert result.exit_code == 0, result.output assert "-X" in cap["argv"] and "-n" in cap["argv"] -def test_vmgui_display_option_overrides_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_display_option_overrides_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") cap = _capture_launch(monkeypatch) monkeypatch.delenv("DISPLAY", raising=False) - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx), "--display", ":1"]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx), "--display", ":1"]) assert result.exit_code == 0, result.output assert cap["env"]["DISPLAY"] == ":1" assert "display=:1" in result.output -def test_vmgui_errors_without_display(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_errors_without_display(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") _capture_launch(monkeypatch) monkeypatch.delenv("DISPLAY", raising=False) - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code != 0 assert "no X display" in result.output -def test_vmgui_preflight_failure_reports_xhost(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_preflight_failure_reports_xhost(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") _capture_launch(monkeypatch) @@ -603,35 +603,35 @@ def test_vmgui_preflight_failure_reports_xhost(monkeypatch: pytest.MonkeyPatch, vm_module, "_x_display_reachable", lambda _env: (False, "cannot open display :0") ) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code != 0 assert "xhost +SI:localuser:ci-runner" in result.output -def test_vmgui_drops_unreadable_xauthority(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_drops_unreadable_xauthority(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") monkeypatch.setenv("XAUTHORITY", str(tmp_path / "missing.Xauthority")) - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code == 0, result.output assert "XAUTHORITY" not in cap["env"] assert "dropping unreadable XAUTHORITY" in result.output -def test_vmgui_immediate_exit_surfaces_log(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_immediate_exit_surfaces_log(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") _capture_launch(monkeypatch, rc=1) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code != 0 assert "exited immediately" in result.output assert "cannot open display" in result.output -def test_vmgui_launch_oserror_is_reported(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_vmware_launch_oserror_is_reported(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: vmx = tmp_path / "clone.vmx" vmx.write_text("config") @@ -642,25 +642,25 @@ def test_vmgui_launch_oserror_is_reported(monkeypatch: pytest.MonkeyPatch, tmp_p monkeypatch.setattr(vm_module, "_x_display_reachable", lambda _env: (True, "")) monkeypatch.setattr(vm_module.time, "sleep", lambda _s: None) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--vmx", str(vmx)]) + result = CliRunner().invoke(cli, ["vmware", "--vmx", str(vmx)]) assert result.exit_code != 0 assert "failed to launch VMware GUI" in result.output -def test_vmgui_without_vmx_launches_bare_gui(monkeypatch: pytest.MonkeyPatch) -> None: +def test_vmware_without_vmx_launches_bare_gui(monkeypatch: pytest.MonkeyPatch) -> None: cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui"]) + result = CliRunner().invoke(cli, ["vmware"]) assert result.exit_code == 0, result.output assert cap["argv"][1:] == [] or cap["argv"][1:] == ["-n"] assert "-x" not in cap["argv"] and "-X" not in cap["argv"] assert "pid 4242" in result.output -def test_vmgui_power_on_ignored_without_vmx(monkeypatch: pytest.MonkeyPatch) -> None: +def test_vmware_power_on_ignored_without_vmx(monkeypatch: pytest.MonkeyPatch) -> None: cap = _capture_launch(monkeypatch) monkeypatch.setenv("DISPLAY", ":0") - result = CliRunner().invoke(cli, ["vmgui", "--power-on"]) + result = CliRunner().invoke(cli, ["vmware", "--power-on"]) assert result.exit_code == 0, result.output assert "-x" not in cap["argv"] assert "ignored without --vmx" in result.output