Add certificate replacement lifecycle
This commit is contained in:
@@ -13,6 +13,7 @@ from .adapters import (
|
||||
ObjectReference,
|
||||
PlanAction,
|
||||
PlanOperation,
|
||||
RetireRequest,
|
||||
)
|
||||
from .dependencies import order_plan_actions
|
||||
from .persistence import SQLiteStateStore
|
||||
@@ -26,6 +27,8 @@ class ExecutionAdapter(Protocol):
|
||||
|
||||
def verify(self, object: NormalizedObject) -> bool: ...
|
||||
|
||||
def retire(self, request: RetireRequest) -> None: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExecutedAction:
|
||||
@@ -271,6 +274,30 @@ def _validated_apply_result(
|
||||
"desired references"
|
||||
)
|
||||
|
||||
if (
|
||||
action.operation != PlanOperation.REPLACE
|
||||
and result.retirements
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Only REPLACE actions may request deferred retirement"
|
||||
)
|
||||
|
||||
for retirement in result.retirements:
|
||||
retired = retirement.object
|
||||
|
||||
if retired.local_key is None:
|
||||
raise RuntimeError(
|
||||
"Deferred retirement requires endpoint-local identity"
|
||||
)
|
||||
|
||||
if (
|
||||
retired.object_type == applied.object_type
|
||||
and retired.local_key == applied.local_key
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Deferred retirement cannot target applied object"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -330,6 +357,7 @@ def _preflight(
|
||||
if action.operation not in {
|
||||
PlanOperation.CREATE,
|
||||
PlanOperation.UPDATE,
|
||||
PlanOperation.REPLACE,
|
||||
}:
|
||||
raise RuntimeError(
|
||||
"Unsupported executable plan operation: "
|
||||
@@ -376,6 +404,10 @@ def execute_endpoint_plans(
|
||||
plans,
|
||||
)
|
||||
applied: list[ExecutedAction] = []
|
||||
pending_retirements: list[
|
||||
tuple[str, RetireRequest]
|
||||
] = []
|
||||
|
||||
local_references: dict[
|
||||
str,
|
||||
_LocalReferenceIndex,
|
||||
@@ -451,6 +483,14 @@ def execute_endpoint_plans(
|
||||
authoritative=True,
|
||||
)
|
||||
|
||||
for retirement in result.retirements:
|
||||
pending_retirements.append(
|
||||
(
|
||||
action.endpoint_id,
|
||||
retirement,
|
||||
)
|
||||
)
|
||||
|
||||
applied.append(
|
||||
ExecutedAction(
|
||||
endpoint_id=action.endpoint_id,
|
||||
@@ -460,6 +500,19 @@ def execute_endpoint_plans(
|
||||
)
|
||||
)
|
||||
|
||||
for endpoint_id, retirement in pending_retirements:
|
||||
adapter = adapters_by_endpoint[endpoint_id]
|
||||
|
||||
retire = getattr(adapter, "retire", None)
|
||||
|
||||
if retire is None:
|
||||
raise RuntimeError(
|
||||
f"{endpoint_id} adapter does not support "
|
||||
"deferred retirement"
|
||||
)
|
||||
|
||||
retire(retirement)
|
||||
|
||||
return ExecutionResult(
|
||||
applied=tuple(applied),
|
||||
skipped=tuple(skipped),
|
||||
|
||||
Reference in New Issue
Block a user