feat: synchronize governance templates in bulk
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from collections.abc import Mapping
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -8,7 +9,12 @@ from typing import Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_admin.backend.governance import create_template, delete_template, update_template
|
||||
from govoplan_admin.backend.governance import (
|
||||
create_template,
|
||||
delete_template,
|
||||
synchronize_templates,
|
||||
update_template,
|
||||
)
|
||||
from govoplan_core.admin.settings import get_system_settings
|
||||
from govoplan_core.auth import ApiPrincipal, has_scope, require_any_scope, require_scope
|
||||
from govoplan_core.audit.logging import audit_from_principal, audit_operation_context
|
||||
@@ -119,6 +125,8 @@ from .schemas import (
|
||||
GovernanceTemplateListDeltaResponse,
|
||||
GovernanceTemplateListResponse,
|
||||
GovernanceTemplateUpdateRequest,
|
||||
GovernanceSynchronizationRequest,
|
||||
GovernanceSynchronizationResponse,
|
||||
MaintenanceModeItem,
|
||||
ModuleCatalogItem,
|
||||
ModuleCatalogResponse,
|
||||
@@ -2105,6 +2113,71 @@ def create_governance_template(
|
||||
return _governance_template_item(session, item)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/system/governance-templates/synchronize",
|
||||
response_model=GovernanceSynchronizationResponse,
|
||||
)
|
||||
def synchronize_governance_templates(
|
||||
payload: GovernanceSynchronizationRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_scope("system:governance:write")),
|
||||
):
|
||||
try:
|
||||
result = synchronize_templates(
|
||||
session,
|
||||
template_ids=payload.template_ids,
|
||||
dry_run=payload.dry_run,
|
||||
)
|
||||
except (AdminConflictError, AdminValidationError) as exc:
|
||||
raise _http_admin_error(exc) from exc
|
||||
counts = Counter(item.status for item in result.outcomes)
|
||||
audit_from_principal(
|
||||
session,
|
||||
principal,
|
||||
action=(
|
||||
"governance_template.synchronization_previewed"
|
||||
if payload.dry_run
|
||||
else "governance_template.synchronized"
|
||||
),
|
||||
scope="system",
|
||||
object_type="governance_template_batch",
|
||||
object_id=result.operation_id,
|
||||
details={
|
||||
"version": result.version,
|
||||
"dry_run": result.dry_run,
|
||||
"template_ids": list(dict.fromkeys(payload.template_ids)),
|
||||
"counts": dict(sorted(counts.items())),
|
||||
"blocked_assignment_ids": [
|
||||
item.assignment_id
|
||||
for item in result.outcomes
|
||||
if item.status in {"blocked", "failed"}
|
||||
],
|
||||
},
|
||||
)
|
||||
session.commit()
|
||||
return GovernanceSynchronizationResponse(
|
||||
version=result.version,
|
||||
operation_id=result.operation_id,
|
||||
dry_run=result.dry_run,
|
||||
outcomes=[
|
||||
{
|
||||
"assignment_id": item.assignment_id,
|
||||
"template_id": item.template_id,
|
||||
"tenant_id": item.tenant_id,
|
||||
"kind": item.kind,
|
||||
"operation": item.operation,
|
||||
"status": item.status,
|
||||
"resource_id": item.resource_id,
|
||||
"blocker_codes": list(item.blocker_codes),
|
||||
"message": item.message,
|
||||
"provenance": dict(item.provenance),
|
||||
}
|
||||
for item in result.outcomes
|
||||
],
|
||||
counts=dict(sorted(counts.items())),
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/system/governance-templates/{template_id}", response_model=GovernanceTemplateItem)
|
||||
def update_governance_template(
|
||||
template_id: str,
|
||||
|
||||
@@ -590,3 +590,31 @@ class GovernanceTemplateUpdateRequest(BaseModel):
|
||||
is_active: bool = True
|
||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||
change_request_id: str | None = None
|
||||
|
||||
|
||||
class GovernanceSynchronizationRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
template_ids: list[str] = Field(min_length=1, max_length=100)
|
||||
dry_run: bool = False
|
||||
|
||||
|
||||
class GovernanceSynchronizationOutcome(BaseModel):
|
||||
assignment_id: str
|
||||
template_id: str
|
||||
tenant_id: str
|
||||
kind: Literal["group", "role"]
|
||||
operation: Literal["upsert", "remove"]
|
||||
status: Literal["created", "updated", "unchanged", "removed", "absent", "blocked", "failed"]
|
||||
resource_id: str | None = None
|
||||
blocker_codes: list[str] = Field(default_factory=list)
|
||||
message: str | None = None
|
||||
provenance: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class GovernanceSynchronizationResponse(BaseModel):
|
||||
version: Literal["1"] = "1"
|
||||
operation_id: str
|
||||
dry_run: bool
|
||||
outcomes: list[GovernanceSynchronizationOutcome]
|
||||
counts: dict[str, int] = Field(default_factory=dict)
|
||||
|
||||
Reference in New Issue
Block a user