This commit is contained in:
its.kstyagi@gmail.com
2026-09-04 20:56:18 +00:00
commit 1022f24c34
43 changed files with 6160 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""FastAPI routers for UVM's local management API."""
+33
View File
@@ -0,0 +1,33 @@
"""Shared FastAPI dependencies for accessing and protecting UVM services."""
from __future__ import annotations
import hmac
from typing import cast
from fastapi import Header, HTTPException, Request, status
from ..app import Application
def get_application(request: Request) -> Application:
return cast(Application, request.app.state.uvm_application)
def get_authorized_application(
request: Request,
x_uvm_token: str | None = Header(default=None),
) -> Application:
application = get_application(request)
expected_token = application.settings.api_token
if not expected_token:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="UVM_API_TOKEN is not configured",
)
if not hmac.compare_digest(x_uvm_token or "", expected_token):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="missing or invalid X-UVM-Token",
)
return application
+15
View File
@@ -0,0 +1,15 @@
"""Unauthenticated health endpoint for local liveness checks."""
from __future__ import annotations
from fastapi import APIRouter
from ..api_models import HealthResponse
router = APIRouter(tags=["health"])
@router.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
return HealthResponse()
+26
View File
@@ -0,0 +1,26 @@
"""Host installation endpoint backed by the existing UVM installer."""
from __future__ import annotations
from fastapi import APIRouter, Depends
from ..api_models import InstallRequest, InstallResponse
from ..app import Application
from .dependencies import get_authorized_application
router = APIRouter(tags=["installation"])
@router.post("/install", response_model=InstallResponse)
def install(
request: InstallRequest,
application: Application = Depends(get_authorized_application),
) -> InstallResponse:
firecracker, assets = application.installer.install(force_assets=request.force)
application.state_store.initialize()
return InstallResponse(
firecracker=str(firecracker),
kernel=str(assets.kernel),
rootfs=str(assets.rootfs),
)
+70
View File
@@ -0,0 +1,70 @@
"""VM lifecycle endpoints backed by the existing UVM lifecycle service."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, status
from ..api_models import DestroyResponse, VmCreateRequest, VmResponse, vm_response
from ..app import Application
from ..domain import VmSpec
from ..validation import parse_cpu, parse_password, parse_ram, parse_username
from .dependencies import get_authorized_application
router = APIRouter(prefix="/vms", tags=["vms"])
@router.get("", response_model=list[VmResponse])
def list_vms(
application: Application = Depends(get_authorized_application),
) -> list[VmResponse]:
return [
vm_response(listed.vm, observed_status=listed.observed_status)
for listed in application.lifecycle.list_vms()
]
@router.post("", response_model=VmResponse, status_code=status.HTTP_201_CREATED)
def create_vm(
request: VmCreateRequest,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
vm = application.lifecycle.create(
VmSpec(
cpu=parse_cpu(str(request.cpu)),
ram_mib=parse_ram(str(request.ram)),
guest_ip=request.guest_ip,
username=parse_username(request.username),
password=parse_password(request.password.get_secret_value()),
)
)
return vm_response(vm)
@router.post("/{vm_id}/stop", response_model=VmResponse)
def stop_vm(
vm_id: str,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
return vm_response(application.lifecycle.stop(vm_id))
@router.delete("/{vm_id}", response_model=DestroyResponse)
def destroy_vm(
vm_id: str,
application: Application = Depends(get_authorized_application),
) -> DestroyResponse:
vm = application.lifecycle.destroy(vm_id)
return DestroyResponse(id=vm.id)
@router.get("/{identifier}", response_model=VmResponse)
def get_vm(
identifier: str,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
for listed in application.lifecycle.list_vms():
vm = listed.vm
if identifier in {vm.id, vm.guest_ip}:
return vm_response(vm, observed_status=listed.observed_status)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="VM not found")