__init__
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""FastAPI routers for UVM's local management API."""
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user