Add a stateful Proxmox API console and broad handler coverage beyond the
initial QEMU slice, backed by imported contracts for majors 6–9. - Implement durable handlers for access/auth, cluster, LXC, storage, HA, firewall, Ceph, SDN, ACME, notifications, pools, mapping, and node ops - Serve an interactive Web UI with catalog browsing, demo seed controls, and OpenAPI/help surfaces - Bundle PVE 6.4-15, 7.4-16, and 8.4.5 contract revisions alongside 9.2.3 - Support in-memory runtime contract Apply (POST /ui/api/contract/apply) so /version and /api2 routes follow the selected major until restart - Expand seed profiles (including demo-cluster), migrations 007–008, TLS gateway config, Compose/Makefile tooling, and compatibility evidence - Tighten .gitignore for macOS, hidden directories (.*/), and local secrets
This commit is contained in:
+53
-50
@@ -2,25 +2,30 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI, Response
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.api.errors import ApiError, api_error_handler, unhandled_exception_handler
|
||||
from app.api.middleware import RequestContextMiddleware
|
||||
from app.api.registry import HandlerRegistry, register_contract_routes
|
||||
from app.compatibility import CompatibilityDimension, build_report, load_evidence_manifest
|
||||
from app.api.openapi import openapi_tag_metadata
|
||||
from app.api.registry import HandlerRegistry
|
||||
from app.config import Settings, get_settings
|
||||
from app.contracts.model import Snapshot
|
||||
from app.contracts.runtime import apply_runtime_contract, contract_store_root
|
||||
from app.db.pool import AsyncpgDatabase, Database
|
||||
from app.handlers.core import build_core_handlers
|
||||
from app.lifespan import DatabaseFactory, WorkerFactory, create_lifespan, default_database_factory
|
||||
from app.logging import configure_logging
|
||||
from app.observability.health import router as health_router
|
||||
from app.simulation.clock import AcceleratedClock
|
||||
from app.tasks.backup import backup_handler
|
||||
from app.tasks.lxc import lxc_handler
|
||||
from app.tasks.qemu import qemu_handler
|
||||
from app.tasks.repository import TaskRepository
|
||||
from app.tasks.worker import TaskWorker
|
||||
from app.web.routes import router as web_router
|
||||
|
||||
|
||||
def create_app(
|
||||
@@ -39,27 +44,45 @@ def create_app(
|
||||
def task_worker(database: Database) -> TaskWorker:
|
||||
adapter = cast(AsyncpgDatabase, database)
|
||||
repository = TaskRepository(adapter.pool)
|
||||
handler = qemu_handler(repository, AcceleratedClock(resolved.simulation_time_scale))
|
||||
clock = AcceleratedClock(resolved.simulation_time_scale)
|
||||
qemu = qemu_handler(repository, clock)
|
||||
lxc = lxc_handler(repository, clock)
|
||||
backup = backup_handler(repository, clock)
|
||||
return TaskWorker(
|
||||
repository,
|
||||
"simulator-worker",
|
||||
{
|
||||
"qemu-clone": handler,
|
||||
"qemu-create": handler,
|
||||
"qemu-delete": handler,
|
||||
"qemu-reboot": handler,
|
||||
"qemu-reset": handler,
|
||||
"qemu-resume": handler,
|
||||
"qemu-shutdown": handler,
|
||||
"qemu-migrate": handler,
|
||||
"qemu-move-disk": handler,
|
||||
"qemu-snapshot-create": handler,
|
||||
"qemu-snapshot-delete": handler,
|
||||
"qemu-snapshot-rollback": handler,
|
||||
"qemu-start": handler,
|
||||
"qemu-stop": handler,
|
||||
"qemu-suspend": handler,
|
||||
"qemu-update": handler,
|
||||
"qemu-clone": qemu,
|
||||
"qemu-create": qemu,
|
||||
"qemu-delete": qemu,
|
||||
"qemu-reboot": qemu,
|
||||
"qemu-reset": qemu,
|
||||
"qemu-resume": qemu,
|
||||
"qemu-shutdown": qemu,
|
||||
"qemu-migrate": qemu,
|
||||
"qemu-move-disk": qemu,
|
||||
"qemu-snapshot-create": qemu,
|
||||
"qemu-snapshot-delete": qemu,
|
||||
"qemu-snapshot-rollback": qemu,
|
||||
"qemu-start": qemu,
|
||||
"qemu-stop": qemu,
|
||||
"qemu-suspend": qemu,
|
||||
"qemu-update": qemu,
|
||||
"lxc-clone": lxc,
|
||||
"lxc-create": lxc,
|
||||
"lxc-delete": lxc,
|
||||
"lxc-migrate": lxc,
|
||||
"lxc-reboot": lxc,
|
||||
"lxc-resume": lxc,
|
||||
"lxc-shutdown": lxc,
|
||||
"lxc-snapshot-create": lxc,
|
||||
"lxc-snapshot-delete": lxc,
|
||||
"lxc-snapshot-rollback": lxc,
|
||||
"lxc-start": lxc,
|
||||
"lxc-stop": lxc,
|
||||
"lxc-suspend": lxc,
|
||||
"vzdump": backup,
|
||||
"aptupdate": backup,
|
||||
},
|
||||
concurrency=resolved.task_worker_concurrency,
|
||||
lease_seconds=resolved.task_lease_seconds,
|
||||
@@ -69,49 +92,29 @@ def create_app(
|
||||
app = FastAPI(
|
||||
title=resolved.app_name,
|
||||
version="0.1.0",
|
||||
openapi_tags=openapi_tag_metadata(),
|
||||
lifespan=create_lifespan(resolved, database_factory, resolved_workers or ()),
|
||||
)
|
||||
app.state.settings = resolved
|
||||
app.state.contract_swap_lock = asyncio.Lock()
|
||||
app.add_middleware(RequestContextMiddleware, header_name=resolved.request_id_header)
|
||||
app.add_exception_handler(Exception, unhandled_exception_handler)
|
||||
app.add_exception_handler(ApiError, api_error_handler)
|
||||
app.include_router(web_router)
|
||||
app.include_router(health_router)
|
||||
if resolved.contract_snapshot is not None:
|
||||
snapshot = Snapshot.model_validate_json(resolved.contract_snapshot.read_bytes())
|
||||
resolved_handlers = handlers or build_core_handlers(resolved)
|
||||
register_contract_routes(
|
||||
apply_runtime_contract(
|
||||
app,
|
||||
snapshot,
|
||||
resolved_handlers,
|
||||
resolved.contract_fallback,
|
||||
handlers=resolved_handlers,
|
||||
store_root=contract_store_root(resolved),
|
||||
fallback=resolved.contract_fallback,
|
||||
settings=resolved,
|
||||
require_evidence_match=True,
|
||||
register_admin=True,
|
||||
)
|
||||
declared = frozenset(
|
||||
(path.path, method.verb) for path in snapshot.paths for method in path.methods
|
||||
)
|
||||
dimensions = {CompatibilityDimension.ROUTE_METHOD: declared}
|
||||
if resolved.compatibility_evidence is not None:
|
||||
evidence = load_evidence_manifest(resolved.compatibility_evidence)
|
||||
if evidence.source_version != snapshot.source_version:
|
||||
raise ValueError("compatibility evidence version does not match contract")
|
||||
dimensions.update(evidence.dimension_map())
|
||||
dimensions[CompatibilityDimension.ROUTE_METHOD] = declared
|
||||
report = build_report(
|
||||
snapshot,
|
||||
implemented=resolved_handlers.keys() & declared,
|
||||
dimensions=dimensions,
|
||||
)
|
||||
|
||||
@app.get("/admin/compatibility", include_in_schema=False)
|
||||
async def compatibility_report() -> dict[str, object]:
|
||||
return report.as_json()
|
||||
|
||||
@app.get("/admin/compatibility.md", include_in_schema=False)
|
||||
async def compatibility_report_markdown() -> Response:
|
||||
return Response(report.as_markdown(), media_type="text/markdown")
|
||||
|
||||
@app.get("/admin/compatibility.html", include_in_schema=False)
|
||||
async def compatibility_report_html() -> Response:
|
||||
return Response(report.as_html(), media_type="text/html")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
Reference in New Issue
Block a user