feat(vmgui): GUI launcher command, VMX optional

Add top-level `vmgui` to open the VMware Workstation GUI (renamed from the
earlier `vm open`). --vmx is optional: with it the GUI opens that VM, without
it the GUI starts bare. Power-on/fullscreen flags ignored (with a notice) when
no VMX is given. Needs an X display — sudo -u ci-runner strips $DISPLAY, so
--display / xhost; see runbook §4.7. 9 tests; suite green ≥90%.
This commit is contained in:
2026-06-08 00:16:50 +02:00
parent dc06a560d8
commit 41d64fc212
4 changed files with 68 additions and 34 deletions
+2 -1
View File
@@ -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
from ci_orchestrator.commands.vm import vm, vmgui
# Re-export so legacy A1 tests that patched ``cli_module.<name>`` keep working.
from ci_orchestrator.commands.wait import ( # noqa: F401 (intentional re-exports)
@@ -54,6 +54,7 @@ cli.add_command(bench)
cli.add_command(smoke)
cli.add_command(validate)
cli.add_command(creds)
cli.add_command(vmgui)
if __name__ == "__main__": # pragma: no cover
+31 -21
View File
@@ -506,13 +506,13 @@ def _launch_gui(argv: list[str], env: dict[str, str]) -> int:
return proc.pid
@vm.command("open")
@click.command("vmgui")
@click.option(
"--vmx",
"--vm-path",
"vmx",
required=True,
help="Path to the VMX file to open in the VMware Workstation GUI.",
default=None,
help="Path to a VMX to open. Omit to launch the GUI with no VM loaded.",
)
@click.option(
"--vmware-path",
@@ -547,25 +547,28 @@ def _launch_gui(argv: list[str], env: dict[str, str]) -> int:
default=None,
help="X display to use (default: inherit $DISPLAY). e.g. ':0'.",
)
def vm_open(
vmx: str,
def vmgui(
vmx: str | None,
vmware_path: str,
power_on: bool,
fullscreen: bool,
new_window: bool,
display: str | None,
) -> None:
"""Open a VMX in the VMware Workstation GUI (run as the ci-runner user).
"""Open the VMware Workstation GUI, optionally on a VMX (run as ci-runner).
Launches the interactive GUI — unlike the headless ``vmrun`` path the rest
of the orchestrator uses. The GUI needs an X display: when invoked through
``sudo -u ci-runner`` the display is usually stripped, so authorise the
service user first, e.g. ``xhost +SI:localuser:ci-runner``, and pass
of the orchestrator uses. With ``--vmx`` the GUI opens that VM; without it
the GUI starts bare (no VM loaded). The GUI needs an X display: when invoked
through ``sudo -u ci-runner`` the display is usually stripped, so authorise
the service user first, e.g. ``xhost +SI:localuser:ci-runner``, and pass
``--display :0`` (or export ``DISPLAY``).
"""
vmx_path = Path(vmx)
if not vmx_path.is_file():
raise click.ClickException(f"VMX file not found: {vmx}")
vmx_path: Path | None = None
if vmx is not None:
vmx_path = Path(vmx)
if not vmx_path.is_file():
raise click.ClickException(f"VMX file not found: {vmx}")
gui = shutil.which(vmware_path) or vmware_path
@@ -574,7 +577,7 @@ def vm_open(
env["DISPLAY"] = display
if not env.get("DISPLAY"):
click.echo(
"[vm open] WARNING: no X display set ($DISPLAY empty and --display "
"[vmgui] WARNING: no X display set ($DISPLAY empty and --display "
"omitted). The GUI will fail to start. Pass --display :0 and run "
"`xhost +SI:localuser:ci-runner` from a desktop session first.",
err=True,
@@ -583,18 +586,25 @@ def vm_open(
argv = [gui]
if new_window:
argv.append("-n")
if fullscreen:
argv.append("-X")
elif power_on:
argv.append("-x")
argv.append(str(vmx_path))
if vmx_path is not None:
# Power-on flags only make sense with a VM to open.
if fullscreen:
argv.append("-X")
elif power_on:
argv.append("-x")
argv.append(str(vmx_path))
elif power_on or fullscreen:
click.echo(
"[vmgui] note: --power-on/--fullscreen ignored without --vmx.",
err=True,
)
click.echo(f"[vm open] launching VMware GUI: {' '.join(argv)}")
click.echo(f"[vmgui] launching VMware GUI: {' '.join(argv)}")
try:
pid = _launch_gui(argv, env)
except (OSError, ValueError) as exc:
raise click.ClickException(f"failed to launch VMware GUI: {exc}") from exc
click.echo(f"[vm open] VMware GUI started (pid {pid}); display={env.get('DISPLAY', '')}")
click.echo(f"[vmgui] VMware GUI started (pid {pid}); display={env.get('DISPLAY', '')}")
__all__ = ["cleanup_orphans", "vm"]
__all__ = ["cleanup_orphans", "vm", "vmgui"]