Phase A1: bootstrap Python ci_orchestrator package
- pyproject.toml (hatchling) with ruff/mypy/pytest dev deps - src/ci_orchestrator/: config, credentials, backends (Protocol + WorkstationVmrunBackend), transport (WinRM via pypsrp, SSH via paramiko) - CLI entry point with PoC 'wait-ready' subcommand (Windows + Linux guests) - tests/python/: 35 unit tests, 83% coverage, all backends/transport mocked - gitea/workflows/lint.yml: new 'python' job (ruff + mypy --strict + pytest --cov-fail-under=70) - config.example.toml + README setup section + .gitignore Python entries Neutral VmBackend Protocol prepared for Phase C ESXi extension. See plans/implementation-plan-A-B.md step A1.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""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.
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user