Files
proxmox-api-simulator/app/handlers/core.py
T
2026-07-13 01:35:01 +03:00

100 lines
3.8 KiB
Python

"""First read/login semantic service handlers."""
from __future__ import annotations
import json
from typing import Any, cast
from fastapi import Request
from app.api.errors import ApiError
from app.api.registry import HandlerRegistry
from app.config import Settings
from app.db.pool import AsyncpgDatabase
from app.handlers.access import register_access_handlers
from app.handlers.qemu import register_qemu_handlers
from app.security.auth import csrf_token, issue_ticket, verify_secret
def _database(request: Request) -> AsyncpgDatabase:
return cast(AsyncpgDatabase, request.app.state.database)
def build_core_handlers(settings: Settings) -> HandlerRegistry:
registry = HandlerRegistry()
async def version(_request: Request, _inputs: dict[str, Any]) -> dict[str, str]:
return {"version": "9.2.3", "release": "9.2", "repoid": "simulator"}
async def login(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
values = cast(dict[str, Any], inputs["values"])
username = str(values["username"])
password = str(values["password"])
row = await _database(request).pool.fetchrow(
"SELECT name, password_hash FROM principals WHERE name=$1", username
)
if (
row is None
or row["password_hash"] is None
or not verify_secret(password, str(row["password_hash"]))
):
raise ApiError(401, "authentication failure")
key = settings.ticket_signing_key.get_secret_value().encode()
ticket = issue_ticket(username, key)
return {
"username": username,
"ticket": ticket,
"CSRFPreventionToken": csrf_token(ticket, key),
"cap": {"vms": {"VM.Audit": 1, "VM.PowerMgmt": 1}},
}
async def nodes(request: Request, _inputs: dict[str, Any]) -> list[dict[str, Any]]:
rows = await _database(request).pool.fetch(
"SELECT name AS node, status FROM nodes ORDER BY name"
)
return [{"node": str(row["node"]), "status": str(row["status"])} for row in rows]
async def node_status(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
node = str(cast(dict[str, Any], inputs["values"])["node"])
row = await _database(request).pool.fetchrow(
"SELECT name, status FROM nodes WHERE name=$1", node
)
if row is None:
raise ApiError(404, "node does not exist")
return {
"status": str(row["status"]),
"node": str(row["name"]),
"uptime": 0,
"cpu": 0.0,
"memory": {"used": 0, "total": 0},
}
async def resources(request: Request, _inputs: dict[str, Any]) -> list[dict[str, Any]]:
rows = await _database(request).pool.fetch(
"""SELECT r.kind AS type, r.external_id, r.state, n.name AS node
FROM resources r JOIN nodes n ON n.id=r.node_id
ORDER BY r.kind, r.external_id"""
)
result: list[dict[str, Any]] = []
for row in rows:
raw_state = row["state"]
state = json.loads(raw_state) if isinstance(raw_state, str) else dict(raw_state)
result.append(
{
"type": str(row["type"]),
"id": f"{row['type']}/{row['external_id']}",
"node": str(row["node"]),
**state,
}
)
return result
registry.register("/version", "GET", version)
registry.register("/access/ticket", "POST", login)
registry.register("/nodes", "GET", nodes)
registry.register("/nodes/{node}/status", "GET", node_status)
registry.register("/cluster/resources", "GET", resources)
register_access_handlers(registry)
register_qemu_handlers(registry)
return registry