The process reads one stage credential file at startup, applies each job through the CLI lifecycle functions, and waits for further jobs. A second stage credential file refuses startup. A local allow flag does not authorize the call. Assistant: grok Assistant-Session: 01a1609f-72b8-7222-9dff-fae300115e42
100 lines
3.8 KiB
Python
100 lines
3.8 KiB
Python
"""Kept-running manifestation of the lifecycle library.
|
|
|
|
This is the same library the CLI uses. The process stays in the job loop
|
|
until the job stream ends. It is not a second implementation and not a
|
|
cluster workload manifest.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from collections.abc import Iterable, Iterator, Mapping
|
|
from typing import Any
|
|
|
|
from secrets_engine import lifecycle as lifecycle_lib
|
|
from secrets_engine.stage_binding import StartupError, load_stage_binding
|
|
|
|
LOCAL_ALLOW_ENV = "SECRETS_ENGINE_LOCAL_ALLOW"
|
|
CATALOG_STAGE = {"build": "build", "test": "test", "production": "prod"}
|
|
|
|
|
|
def local_allow_requested(flag: bool = False) -> bool:
|
|
"""True when a caller asked to be allowed. The answer is not authority."""
|
|
env = os.environ.get(LOCAL_ALLOW_ENV, "").strip() in {"1", "true", "yes"}
|
|
return bool(flag) or env
|
|
|
|
|
|
def service_jobs(
|
|
catalog_id: str, operation: str, lines: Iterable[str]
|
|
) -> Iterator[dict[str, str]]:
|
|
"""Yield the argv job, then block on further lines until the stream ends."""
|
|
yield {"catalog_id": catalog_id, "operation": operation, "confirm_destroy": ""}
|
|
for line in lines:
|
|
text = line.strip()
|
|
if not text:
|
|
continue
|
|
try:
|
|
payload = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
raise StartupError("service job is not a JSON object") from exc
|
|
if not isinstance(payload, dict):
|
|
raise StartupError("service job must be a mapping")
|
|
next_id = str(payload.get("catalog_id") or "")
|
|
next_operation = str(payload.get("operation") or "")
|
|
if not next_id or not next_operation:
|
|
raise StartupError("service job is missing catalog_id or operation")
|
|
yield {
|
|
"catalog_id": next_id,
|
|
"operation": next_operation,
|
|
"confirm_destroy": str(payload.get("confirm_destroy") or ""),
|
|
}
|
|
|
|
|
|
def run_service(
|
|
cfg: Any,
|
|
jobs: Iterable[Mapping[str, str]],
|
|
*,
|
|
allow: bool = False,
|
|
auth_args: Any = None,
|
|
) -> list[tuple[Any, Any]]:
|
|
"""Run lifecycle jobs until the iterator ends.
|
|
|
|
Startup reads the stage credential files. Two files refuse startup before
|
|
any job is pulled. Each job calls ``build_lifecycle_plan`` and then the
|
|
live lifecycle function the CLI uses, which applies the plan. ``allow``
|
|
is discarded and does not authorize that call.
|
|
"""
|
|
local_allow_requested(allow)
|
|
binding = load_stage_binding()
|
|
if binding is None or not binding.credential or not binding.stage:
|
|
raise StartupError("startup refused: no stage credential is present")
|
|
catalog_stage = CATALOG_STAGE.get(binding.stage, "")
|
|
from types import SimpleNamespace
|
|
|
|
from secrets_engine.catalog import get_entry
|
|
from secrets_engine.cli import _privileged_evidence, execute_lifecycle_plan
|
|
|
|
outcomes: list[tuple[Any, Any]] = []
|
|
for job in jobs:
|
|
entry = get_entry(cfg.catalog_dir, str(job.get("catalog_id") or ""))
|
|
if entry.stage != catalog_stage:
|
|
raise StartupError(
|
|
"startup refused: lane stage does not match the injected stage"
|
|
)
|
|
operation = str(job.get("operation") or "")
|
|
plan = lifecycle_lib.build_lifecycle_plan(entry, operation)
|
|
call = SimpleNamespace(
|
|
operation=operation,
|
|
auth=getattr(auth_args, "auth", "env"),
|
|
bootstrap_token_file=getattr(auth_args, "bootstrap_token_file", None),
|
|
confirm_destroy=str(job.get("confirm_destroy") or ""),
|
|
)
|
|
with _privileged_evidence(
|
|
cfg,
|
|
entry,
|
|
f"lifecycle-{operation}",
|
|
detail={"operation": operation},
|
|
) as evidence:
|
|
result = execute_lifecycle_plan(cfg, call, entry, plan, evidence)
|
|
outcomes.append((plan, result))
|
|
return outcomes
|