__init__
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
"""Configuration and filesystem layout for the uvm command-line application."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
from pathlib import Path
|
||||
|
||||
from .errors import ConfigurationError
|
||||
|
||||
|
||||
DEFAULT_KERNEL_URL = (
|
||||
"https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/"
|
||||
"x86_64/kernels/vmlinux.bin"
|
||||
)
|
||||
DEFAULT_ROOTFS_URL = (
|
||||
"https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/"
|
||||
"x86_64/rootfs/bionic.rootfs.ext4"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Settings:
|
||||
"""All host-specific values consumed by the application."""
|
||||
|
||||
app_name: str = "uvm"
|
||||
base: Path = Path("/var/lib/uvm")
|
||||
network: IPv4Network = IPv4Network("10.42.0.0/24")
|
||||
gateway: IPv4Address = IPv4Address("10.42.0.1")
|
||||
bridge: str = "uvm0"
|
||||
firecracker_version: str = "v1.16.1"
|
||||
default_ram_mib: int = 512
|
||||
default_vcpu: float = 1
|
||||
default_ssh_user: str = "root"
|
||||
kernel_url: str = DEFAULT_KERNEL_URL
|
||||
rootfs_url: str = DEFAULT_ROOTFS_URL
|
||||
firecracker_sha256: str | None = None
|
||||
firecracker_binary_sha256: str | None = None
|
||||
kernel_sha256: str | None = None
|
||||
rootfs_sha256: str | None = None
|
||||
allow_unverified_downloads: bool = False
|
||||
api_token: str | None = None
|
||||
api_tls_cert: Path | None = None
|
||||
api_tls_key: Path | None = None
|
||||
api_timeout_s: float = 2.0
|
||||
api_socket_timeout_s: float = 5.0
|
||||
terminate_timeout_s: float = 5.0
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.gateway not in self.network:
|
||||
raise ConfigurationError(
|
||||
f"gateway {self.gateway} is outside configured network {self.network}"
|
||||
)
|
||||
if self.gateway in (self.network.network_address, self.network.broadcast_address):
|
||||
raise ConfigurationError("gateway must be a usable host address")
|
||||
if len(self.bridge) > 15:
|
||||
raise ConfigurationError("bridge name must be 15 characters or fewer")
|
||||
if self.api_token is not None:
|
||||
if not self.api_token or not all(33 <= ord(character) <= 126 for character in self.api_token):
|
||||
raise ConfigurationError(
|
||||
"UVM_API_TOKEN must contain only visible ASCII characters without whitespace"
|
||||
)
|
||||
|
||||
@property
|
||||
def bin_dir(self) -> Path:
|
||||
return self.base / "bin"
|
||||
|
||||
@property
|
||||
def images_dir(self) -> Path:
|
||||
return self.base / "images"
|
||||
|
||||
@property
|
||||
def vms_dir(self) -> Path:
|
||||
return self.base / "vms"
|
||||
|
||||
@property
|
||||
def state_path(self) -> Path:
|
||||
return self.base / "state.json"
|
||||
|
||||
@property
|
||||
def state_lock_path(self) -> Path:
|
||||
return self.base / "state.lock"
|
||||
|
||||
@property
|
||||
def operation_lock_path(self) -> Path:
|
||||
return self.base / "operations.lock"
|
||||
|
||||
@property
|
||||
def firecracker_binary(self) -> Path:
|
||||
return self.bin_dir / "firecracker"
|
||||
|
||||
@property
|
||||
def jailer_binary(self) -> Path:
|
||||
return self.bin_dir / "jailer"
|
||||
|
||||
@property
|
||||
def kernel_image(self) -> Path:
|
||||
return self.images_dir / "vmlinux"
|
||||
|
||||
@property
|
||||
def rootfs_image(self) -> Path:
|
||||
return self.images_dir / "ubuntu.ext4"
|
||||
|
||||
@property
|
||||
def integrity_manifest_path(self) -> Path:
|
||||
return self.base / "integrity.json"
|
||||
|
||||
def vm_dir(self, vm_id: str) -> Path:
|
||||
return self.vms_dir / vm_id
|
||||
|
||||
@classmethod
|
||||
def from_environment(cls) -> "Settings":
|
||||
"""Load optional deployment overrides while retaining script defaults."""
|
||||
|
||||
try:
|
||||
network = IPv4Network(os.environ.get("UVM_NETWORK", "10.42.0.0/24"))
|
||||
gateway = IPv4Address(os.environ.get("UVM_GATEWAY", "10.42.0.1"))
|
||||
except ValueError as error:
|
||||
raise ConfigurationError(f"invalid network configuration: {error}") from error
|
||||
|
||||
return cls(
|
||||
base=Path(os.environ.get("UVM_BASE", "/var/lib/uvm")).expanduser(),
|
||||
network=network,
|
||||
gateway=gateway,
|
||||
bridge=os.environ.get("UVM_BRIDGE", "uvm0"),
|
||||
kernel_url=os.environ.get("UVM_KERNEL_URL", DEFAULT_KERNEL_URL),
|
||||
rootfs_url=os.environ.get("UVM_ROOTFS_URL", DEFAULT_ROOTFS_URL),
|
||||
firecracker_sha256=_optional_environment_value("UVM_FIRECRACKER_SHA256"),
|
||||
firecracker_binary_sha256=_optional_environment_value(
|
||||
"UVM_FIRECRACKER_BINARY_SHA256"
|
||||
),
|
||||
kernel_sha256=_optional_environment_value("UVM_KERNEL_SHA256"),
|
||||
rootfs_sha256=_optional_environment_value("UVM_ROOTFS_SHA256"),
|
||||
allow_unverified_downloads=_boolean_environment_value(
|
||||
"UVM_ALLOW_UNVERIFIED_DOWNLOADS", default=False
|
||||
),
|
||||
api_token=_optional_environment_value("UVM_API_TOKEN"),
|
||||
api_tls_cert=_optional_environment_path("UVM_API_TLS_CERT"),
|
||||
api_tls_key=_optional_environment_path("UVM_API_TLS_KEY"),
|
||||
)
|
||||
|
||||
|
||||
def _optional_environment_value(name: str) -> str | None:
|
||||
value = os.environ.get(name)
|
||||
return value if value else None
|
||||
|
||||
|
||||
def _optional_environment_path(name: str) -> Path | None:
|
||||
value = _optional_environment_value(name)
|
||||
return Path(value).expanduser() if value else None
|
||||
|
||||
|
||||
def _boolean_environment_value(name: str, *, default: bool) -> bool:
|
||||
value = os.environ.get(name)
|
||||
if value is None:
|
||||
return default
|
||||
normalized = value.strip().lower()
|
||||
if normalized in {"1", "true", "yes", "on"}:
|
||||
return True
|
||||
if normalized in {"0", "false", "no", "off"}:
|
||||
return False
|
||||
raise ConfigurationError(f"{name} must be one of true/false, yes/no, or 1/0")
|
||||
Reference in New Issue
Block a user