193 lines
5.5 KiB
Python
193 lines
5.5 KiB
Python
"""
|
|
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="128G", description="Root disk size (e.g. 128G)"
|
|
)
|
|
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"
|
|
)
|
|
bridge: str = Field(
|
|
default="vmbr1", description="Network bridge to attach the VM to"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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
|