"""Pydantic request and response models for the UVM HTTP API.""" from __future__ import annotations from ipaddress import IPv4Address from typing import Literal from pydantic import BaseModel, ConfigDict, Field, SecretStr from .domain import VmRecord class HealthResponse(BaseModel): status: str = "ok" class ServerExitResponse(BaseModel): status: str = "stopping" class InstallRequest(BaseModel): model_config = ConfigDict(extra="forbid") force: bool = False class InstallResponse(BaseModel): firecracker: str kernel: str rootfs: str class PortRuleModel(BaseModel): """A private guest TCP port; HTTP and HTTPS are descriptive labels only.""" model_config = ConfigDict(extra="forbid") port: int = Field(strict=True, ge=1, le=65535) protocol: Literal["TCP", "HTTP", "HTTPS"] = "TCP" class VmCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") cpu: float = Field(default=1, gt=0) ram: str | int | float = "512" storage: str | int | float = Field(default="30G", description="Disk capacity, minimum 30 GiB; bare numbers use GiB") guest_ip: IPv4Address | None = None username: str = "root" password: SecretStr = SecretStr("root") name: str | None = None ports: list[PortRuleModel] | None = None no_default_ports: bool = Field(default=False, strict=True) class VmResizeRequest(BaseModel): model_config = ConfigDict(extra="forbid") cpu: float | None = Field(default=None, gt=0, allow_inf_nan=False, strict=True) ram: str | int | float | None = None storage: str | int | float | None = Field(default=None, description="Disk capacity, minimum 30 GiB; bare numbers use GiB") class VmResourcesModel(BaseModel): cpu: float ram_mib: int storage_mib: int class VmResponse(BaseModel): id: str name: str | None cpu: float ram_mib: int storage_mib: int | None pending_resize: VmResourcesModel | None guest_ip: str gateway: str mac: str username: str status: str observed_status: str ports: list[PortRuleModel] | None policy_status: str operation: str | None pid: int | None created_at: int updated_at: int last_error: str | None class DestroyResponse(BaseModel): id: str status: str = "terminated" class PortUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") mode: Literal["set", "open", "close"] = "set" ports: list[PortRuleModel] = Field(default_factory=list) class PortSyncResponse(BaseModel): id: str policy_status: str status: str class VmLogResponse(BaseModel): content: str truncated: bool class DiagnosticResponse(BaseModel): name: str ok: bool message: str class VmBulkActionRequest(BaseModel): model_config = ConfigDict(extra="forbid") action: Literal["start", "stop", "restart", "delete", "ports-sync"] identifiers: list[str] = Field(default_factory=list) all_vms: bool = Field(default=False, strict=True) timeout: float | None = None class VmActionResultResponse(BaseModel): id: str action: str outcome: str error: str | None = None def vm_response( vm: VmRecord, *, observed_status: str | None = None, policy_status: str = "unknown", ) -> VmResponse: return VmResponse( id=vm.id, name=vm.name, cpu=vm.cpu, ram_mib=vm.ram_mib, storage_mib=vm.storage_mib, pending_resize=VmResourcesModel(**vm.pending_resize.to_dict()) if vm.pending_resize is not None else None, guest_ip=vm.guest_ip, gateway=vm.gateway, mac=vm.mac, username=vm.username, status=vm.status, observed_status=observed_status if observed_status is not None else vm.status, ports=( [PortRuleModel(port=rule.port, protocol=rule.protocol) for rule in vm.ports] if vm.ports is not None else None ), policy_status=policy_status, operation=vm.operation, pid=vm.pid, created_at=vm.created_at, updated_at=vm.updated_at, last_error=vm.last_error, )