Initial curated Unraid-AppOps-Gateway source from 3bd6f6fee310125bfbb4d4650389a817f498724d
This commit is contained in:
commit
1dfbca08a1
528 files changed
+131054
No files matched your search
@@ -0,0 +1,140 @@
|
||||
"""Operator API for Deployment Control Plane V1.
|
||||
|
||||
All mutations accept a typed plan and an idempotency key. There is no endpoint
|
||||
for command execution, Compose upload, arbitrary host paths or raw secrets.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlmodel import Session
|
||||
|
||||
from ..core.config import settings
|
||||
from ..core.security import require_operator_request
|
||||
from ..db.session import get_session
|
||||
from ..schemas.deployment import DeploymentPlanV1
|
||||
from ..services.deployment_control_plane import (
|
||||
DeploymentControlError,
|
||||
DeploymentControlPlane,
|
||||
)
|
||||
from ..services.deployment_broker import DeploymentBrokerClient
|
||||
|
||||
|
||||
def _require_deployment_operator(request: Request) -> str:
|
||||
if not settings.operator_auth_required:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Deployment Control Plane requires the configured operator authentication boundary.",
|
||||
)
|
||||
return require_operator_request(request)
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/deployments",
|
||||
tags=["deployments"],
|
||||
dependencies=[Depends(_require_deployment_operator)],
|
||||
)
|
||||
|
||||
|
||||
class DeploymentRequestV1(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
plan: DeploymentPlanV1
|
||||
operation: Literal["preflight", "apply", "rollback"] = "preflight"
|
||||
|
||||
|
||||
class WorkMeshPromotionPreflightRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
revision: str = Field(pattern=r"^[0-9a-f]{40}$")
|
||||
|
||||
|
||||
DeploymentSession = Annotated[Session, Depends(get_session)]
|
||||
DeploymentIdempotencyKey = Annotated[str | None, Header(alias="Idempotency-Key")]
|
||||
DeploymentLimit = Annotated[int, Query(ge=1, le=250)]
|
||||
|
||||
|
||||
def _idempotency_header(value: str | None) -> str:
|
||||
if not value:
|
||||
raise HTTPException(status_code=400, detail="Idempotency-Key is required for deployment operations.")
|
||||
return value.strip()
|
||||
|
||||
|
||||
@router.post("")
|
||||
def request_deployment(
|
||||
payload: DeploymentRequestV1,
|
||||
session: DeploymentSession,
|
||||
idempotency_key: DeploymentIdempotencyKey = None,
|
||||
):
|
||||
try:
|
||||
return DeploymentControlPlane().execute(
|
||||
session,
|
||||
plan=payload.plan,
|
||||
idempotency_key=_idempotency_header(idempotency_key),
|
||||
operation=payload.operation,
|
||||
)
|
||||
except DeploymentControlError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/workmesh/promotions/preflight")
|
||||
def preflight_workmesh_promotion(
|
||||
payload: WorkMeshPromotionPreflightRequest,
|
||||
session: DeploymentSession,
|
||||
idempotency_key: DeploymentIdempotencyKey = None,
|
||||
):
|
||||
from ..services.workmesh_promotion import (
|
||||
WorkMeshPromotionError,
|
||||
WorkMeshPromotionService,
|
||||
)
|
||||
|
||||
try:
|
||||
return WorkMeshPromotionService().preflight(
|
||||
session,
|
||||
revision=payload.revision,
|
||||
idempotency_key=_idempotency_header(idempotency_key),
|
||||
)
|
||||
except WorkMeshPromotionError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/workmesh/promotions/{promotion_id}")
|
||||
def get_workmesh_promotion(promotion_id: str, session: DeploymentSession):
|
||||
from ..services.workmesh_promotion import WorkMeshPromotionService
|
||||
|
||||
result = WorkMeshPromotionService().refresh(session, promotion_id)
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="WorkMesh promotion not found.")
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/workmesh/promotions")
|
||||
def list_workmesh_promotions(
|
||||
session: DeploymentSession,
|
||||
limit: DeploymentLimit = 20,
|
||||
):
|
||||
from ..services.workmesh_promotion import WorkMeshPromotionService
|
||||
|
||||
return {"items": WorkMeshPromotionService.list_promotions(session, limit=limit)}
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_deployments(session: DeploymentSession, limit: DeploymentLimit = 100):
|
||||
return {"items": DeploymentControlPlane().list_jobs(session, limit=limit)}
|
||||
|
||||
|
||||
@router.get("/delivery-status")
|
||||
def get_delivery_status():
|
||||
"""Expose the controller's bounded, secret-free runnerless delivery view."""
|
||||
return DeploymentBrokerClient().delivery_status()
|
||||
|
||||
|
||||
@router.get("/{job_id}")
|
||||
def get_deployment(job_id: str, session: DeploymentSession):
|
||||
try:
|
||||
return DeploymentControlPlane().refresh(session, job_id)
|
||||
except DeploymentControlError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
Reference in new issue
Block a user