"""Neutral VM backend Protocol. Phase C hook: keep names hypervisor-agnostic. ``clone_linked``, ``start``, ``stop``, ``delete``, ``get_ip``, ``list_snapshots`` map cleanly onto both ``vmrun`` and pyVmomi (vSphere). Identifiers are opaque strings (a VMX path on Workstation, a managed object reference on ESXi). """ from __future__ import annotations from dataclasses import dataclass from enum import StrEnum from typing import Protocol class VmState(StrEnum): """Coarse VM power state, normalised across backends.""" POWERED_OFF = "powered_off" POWERED_ON = "powered_on" SUSPENDED = "suspended" UNKNOWN = "unknown" @dataclass(frozen=True) class VmHandle: """Opaque reference to a VM managed by a backend. ``identifier`` is backend-specific (a VMX path for Workstation, a managed object reference for vSphere). Callers MUST treat it as opaque. """ identifier: str name: str | None = None class VmBackend(Protocol): """Hypervisor-agnostic VM operations. All methods raise :class:`~ci_orchestrator.backends.errors.BackendError` on failure. Implementations are free to add backend-specific kwargs but MUST honour the documented signature for the listed parameters. """ def clone_linked( self, template: str, snapshot: str, name: str, destination: str | None = None, ) -> VmHandle: """Create a linked clone of ``template`` at snapshot ``snapshot``.""" ... def start(self, handle: VmHandle, headless: bool = True) -> None: """Power on the VM.""" ... def stop(self, handle: VmHandle, hard: bool = False) -> None: """Power off the VM. ``hard=True`` requests a forced power-off.""" ... def delete(self, handle: VmHandle) -> None: """Remove the VM from inventory and delete its files.""" ... def get_ip(self, handle: VmHandle, timeout: float = 0.0) -> str | None: """Return the guest's primary IP, or ``None`` if not yet known.""" ... def list_snapshots(self, handle: VmHandle) -> list[str]: """Return snapshot names defined on ``handle``.""" ... def is_running(self, handle: VmHandle) -> bool: """Return ``True`` if the VM appears in the running-VM list. Implementations MUST NOT rely on guest tools (e.g. ``vmrun getGuestIPAddress``) for this check — see ``AGENTS.md`` error #10. """ ...