feat: Bereitstellungsvorlage-Erstellung per API (schliesst Phase-5-Luecke)
Bislang gab es dafuer keinen API-/UI-Weg - Bereitstellungsvorlagen kamen
nur per Seed-Migration (0005/0011) fuer die "Default Lab"-Organisation
zustande. Eine wirklich neue Kundenorganisation hatte dadurch eine leere
Vorlagenauswahl auf der Kundenplattform-Installationsmedium-Seite (in
Phase 5 als offene Luecke dokumentiert).
Neuer POST /api/v1/organizations/{id}/bereitstellungsvorlagen: waehlt
Workspace + Backend, einfache Root-Dateisystem-Wahl (single-scheme,
ext4/btrfs - ein custom-Schema mit extra_partitions bleibt Sache
direkter DB-Pflege), optional Default/Diskverschluesselung/Secure-Boot.
Beachtet den bestehenden Unique-Index (nur eine is_default=TRUE-Zeile je
Organisation) durch expliziten Zwei-Schritte-Reset, gleiches Muster wie
Migration 0011.
Lokal end-to-end verifiziert: frische Organisation ohne jede Vorlage,
zwei Vorlagen nacheinander angelegt (zweite mit is_default=true kippt
die erste korrekt auf false), Partitioning/Diskverschluesselung/
Secure-Boot-Flags korrekt in der DB, Kundenplattform-Installationsmedium-
Seite zeigt die neue Vorlage danach korrekt in der Auswahl.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
122e9fb499
commit
fb175bf861
86
app.py
86
app.py
@ -116,6 +116,16 @@ class CreateWorkspaceRequest(BaseModel):
|
||||
organization_id: str | None = None
|
||||
|
||||
|
||||
class CreateBereitstellungsvorlageRequest(BaseModel):
|
||||
workspace_id: str
|
||||
backend_id: str
|
||||
label: str
|
||||
is_default: bool = False
|
||||
disk_encryption: bool = False
|
||||
root_filesystem: str = "ext4"
|
||||
secure_boot_required: bool = False
|
||||
|
||||
|
||||
class CreateEnrollmentSessionRequest(BaseModel):
|
||||
bereitstellungsvorlage_id: str
|
||||
max_devices: int
|
||||
@ -467,6 +477,56 @@ def fetch_templates(organization_id: str) -> list[dict]:
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def create_bereitstellungsvorlage(
|
||||
organization_id: str,
|
||||
workspace_id: str,
|
||||
backend_id: str,
|
||||
label: str,
|
||||
is_default: bool,
|
||||
disk_encryption: bool,
|
||||
root_filesystem: str,
|
||||
secure_boot_required: bool,
|
||||
) -> dict:
|
||||
"""
|
||||
Bislang gab es dafuer keinen API-/UI-Weg - Bereitstellungsvorlagen kamen
|
||||
nur per Seed-Migration (0005/0011) fuer die "Default Lab"-Organisation
|
||||
zustande (siehe Phase 5 des Self-Service-ISO-Plans, dort als offene
|
||||
Luecke notiert). partitioning bekommt bewusst nur eine einfache
|
||||
"root_filesystem"-Wahl im single-scheme (siehe Migration 0015 fuer die
|
||||
volle Form) - ein custom-Schema mit extra_partitions ist Sache der
|
||||
direkten DB-Pflege, nicht dieses Formulars.
|
||||
"""
|
||||
|
||||
vorlage_id = uuid4()
|
||||
partitioning = {"scheme": "single", "root_filesystem": root_filesystem}
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if is_default:
|
||||
# Der Unique-Index bereitstellungsvorlagen_one_default_per_org
|
||||
# erlaubt nur eine is_default=TRUE-Zeile je Organisation -
|
||||
# bestehenden Default zuerst explizit zuruecksetzen (gleiches
|
||||
# Zwei-Schritte-Muster wie Migration 0011).
|
||||
cur.execute(
|
||||
"UPDATE bereitstellungsvorlagen SET is_default = FALSE WHERE organization_id = %s AND is_default",
|
||||
(organization_id,),
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO bereitstellungsvorlagen
|
||||
(id, organization_id, workspace_id, backend_id, label, is_default,
|
||||
disk_encryption, partitioning, secure_boot_required)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""",
|
||||
(
|
||||
vorlage_id, organization_id, workspace_id, backend_id, label, is_default,
|
||||
disk_encryption, Jsonb(partitioning), secure_boot_required,
|
||||
),
|
||||
)
|
||||
|
||||
return {"id": str(vorlage_id), "label": label, "is_default": is_default}
|
||||
|
||||
def require_service_token(authorization: str | None) -> bool:
|
||||
"""
|
||||
Akzeptiert entweder den Betreiber-Admin-Token oder das separate
|
||||
@ -1899,6 +1959,32 @@ def list_bereitstellungsvorlagen(organization_id: str, authorization: str | None
|
||||
return {"success": True, "bereitstellungsvorlagen": fetch_templates(organization_id)}
|
||||
|
||||
|
||||
@app.post("/api/v1/organizations/{organization_id}/bereitstellungsvorlagen")
|
||||
def create_bereitstellungsvorlage_endpoint(
|
||||
organization_id: str,
|
||||
payload: CreateBereitstellungsvorlageRequest,
|
||||
authorization: str | None = Header(default=None),
|
||||
):
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
if not payload.label.strip():
|
||||
return {"success": False, "error": "invalid_request", "message": "label darf nicht leer sein."}
|
||||
|
||||
vorlage = create_bereitstellungsvorlage(
|
||||
organization_id,
|
||||
payload.workspace_id,
|
||||
payload.backend_id,
|
||||
payload.label,
|
||||
payload.is_default,
|
||||
payload.disk_encryption,
|
||||
payload.root_filesystem,
|
||||
payload.secure_boot_required,
|
||||
)
|
||||
|
||||
return {"success": True, "bereitstellungsvorlage": vorlage}
|
||||
|
||||
|
||||
@app.get("/api/v1/organizations/{organization_id}/devices/unassigned")
|
||||
def list_unassigned_devices(organization_id: str, authorization: str | None = Header(default=None)):
|
||||
if not require_service_token(authorization):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user