""" Sovereign Orchestrator - Pydantic Models Data models for API request/response payloads and internal state. """ from __future__ import annotations import uuid from datetime import datetime, timezone from enum import Enum from typing import Optional from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # Enums # --------------------------------------------------------------------------- class BuildState(str, Enum): """Lifecycle states for an ISO build job.""" PENDING = "pending" BUILDING = "building" UPLOADING = "uploading" COMPLETED = "completed" FAILED = "failed" class NetworkSource(str, Enum): """Proxmox installer network source options.""" FROM_DHCP = "from-dhcp" FROM_ANSWER = "from-answer" class Filesystem(str, Enum): """Supported root filesystems.""" EXT4 = "ext4" XFS = "xfs" ZFS = "zfs" BTRFS = "btrfs" # --------------------------------------------------------------------------- # Request models # --------------------------------------------------------------------------- class ISOConfig(BaseModel): """Configuration payload for generating a custom auto-install ISO.""" fqdn: str = Field( ..., description="Fully qualified domain name for the new host", examples=["pve-test.internal.718it.biz"], ) keyboard: str = Field(default="en-us", description="Keyboard layout") country: str = Field(default="us", description="Country code") timezone: str = Field( default="America/New_York", description="Timezone string" ) mailto: str = Field( default="wmantly@gmail.com", description="Admin email address" ) root_password: str = Field( ..., description="Root password for the installed system" ) root_ssh_keys: list[str] = Field( default_factory=list, description="SSH public keys to authorize for root", ) network_source: NetworkSource = Field( default=NetworkSource.FROM_DHCP, description="Network configuration source", ) filesystem: Filesystem = Field( default=Filesystem.EXT4, description="Root filesystem type" ) disk_list: list[str] = Field( default_factory=lambda: ["sda"], description="Disks to use for installation", ) reboot_on_error: bool = Field( default=False, description="Reboot automatically on install error" ) class DeployConfig(BaseModel): """Configuration for deploying a VM with the custom ISO.""" vmid: int = Field(default=900, description="VM ID to create") node: str = Field(default="dl380-0", description="Proxmox node name") cores: int = Field(default=4, ge=1, le=128, description="CPU cores") memory: int = Field( default=8192, ge=512, description="Memory in MiB" ) disk_size: str = Field( default="64G", description="Root disk size (e.g. 64G)" ) build_id: Optional[str] = Field( default=None, description="Build ID whose ISO to use. Defaults to latest.", ) storage: str = Field( default="local-lvm", description="Proxmox storage for the VM disk" ) iso_storage: str = Field( default="local", description="Proxmox storage containing ISOs" ) # --------------------------------------------------------------------------- # Internal / response models # --------------------------------------------------------------------------- class BuildStatus(BaseModel): """Tracks the state of an ISO build job.""" id: str = Field(default_factory=lambda: uuid.uuid4().hex[:12]) state: BuildState = BuildState.PENDING iso_config: Optional[ISOConfig] = None iso_filename: Optional[str] = None logs: list[str] = Field(default_factory=list) created_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc) ) updated_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc) ) error: Optional[str] = None def log(self, message: str) -> None: """Append a timestamped log line.""" ts = datetime.now(timezone.utc).isoformat() self.logs.append(f"[{ts}] {message}") self.updated_at = datetime.now(timezone.utc) class VMStatus(BaseModel): """Proxmox VM status snapshot.""" vmid: int name: Optional[str] = None status: str node: str cpus: Optional[int] = None maxmem: Optional[int] = None maxdisk: Optional[int] = None uptime: Optional[int] = None pid: Optional[int] = None class NodeInfo(BaseModel): """Proxmox node summary.""" node: str status: str cpu: Optional[float] = None maxcpu: Optional[int] = None mem: Optional[int] = None maxmem: Optional[int] = None uptime: Optional[int] = None class ISOInfo(BaseModel): """ISO file metadata.""" volid: str filename: str size: Optional[int] = None class SystemStatus(BaseModel): """Aggregate system status returned by /api/status.""" nodes: list[NodeInfo] = Field(default_factory=list) test_vm: Optional[VMStatus] = None available_isos: list[ISOInfo] = Field(default_factory=list) active_builds: list[BuildStatus] = Field(default_factory=list) proxmox_connected: bool = False error: Optional[str] = None class DeployResult(BaseModel): """Response from a deploy operation.""" vmid: int node: str status: str message: str task_id: Optional[str] = None