72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
|
|
"""Kept-running manifestation of the lifecycle library.
|
||
|
|
|
||
|
|
This is the same library the CLI uses, in a process that stays up for one
|
||
|
|
injected stage. It is not a second implementation and not a Deployment.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
from collections.abc import Iterable, Mapping
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from secrets_engine import lifecycle as lifecycle_lib
|
||
|
|
from secrets_engine.catalog import CatalogEntry
|
||
|
|
from secrets_engine.lifecycle import LifecyclePlan
|
||
|
|
from secrets_engine.stage_binding import StageBinding, StartupError
|
||
|
|
|
||
|
|
LOCAL_ALLOW_ENV = "SECRETS_ENGINE_LOCAL_ALLOW"
|
||
|
|
|
||
|
|
|
||
|
|
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 run_service(
|
||
|
|
binding: StageBinding | None,
|
||
|
|
jobs: Iterable[Mapping[str, Any]],
|
||
|
|
) -> list[LifecyclePlan]:
|
||
|
|
"""Run lifecycle jobs for one stage until the job iterable ends.
|
||
|
|
|
||
|
|
A job may carry ``foreign_stage_credential`` only as a refusal test. A
|
||
|
|
present value refuses the job. The binding credential is never rendered.
|
||
|
|
"""
|
||
|
|
if binding is None or not binding.credential or not binding.stage:
|
||
|
|
raise StartupError("startup refused: no stage credential is present")
|
||
|
|
plans: list[LifecyclePlan] = []
|
||
|
|
for job in jobs:
|
||
|
|
foreign = str(job.get("foreign_stage_credential") or "")
|
||
|
|
if foreign:
|
||
|
|
raise StartupError(
|
||
|
|
"startup refused: another stage credential is present"
|
||
|
|
)
|
||
|
|
entry = job.get("entry")
|
||
|
|
if not isinstance(entry, CatalogEntry):
|
||
|
|
raise StartupError("service job is missing a catalog entry")
|
||
|
|
operation = str(job.get("operation") or "")
|
||
|
|
plans.append(lifecycle_lib.build_lifecycle_plan(entry, operation))
|
||
|
|
return plans
|
||
|
|
|
||
|
|
|
||
|
|
def invoke_protected(
|
||
|
|
cfg: Any,
|
||
|
|
entry: CatalogEntry,
|
||
|
|
action: str,
|
||
|
|
*,
|
||
|
|
allow: bool = False,
|
||
|
|
auth_args: Any = None,
|
||
|
|
) -> Any:
|
||
|
|
"""Gate a protected call the way the CLI does.
|
||
|
|
|
||
|
|
``allow`` and ``SECRETS_ENGINE_LOCAL_ALLOW`` are recorded as not authority
|
||
|
|
and then discarded. The call still requires a decision record or the
|
||
|
|
published unreachable-engine stance.
|
||
|
|
"""
|
||
|
|
local_allow_requested(allow)
|
||
|
|
from secrets_engine.cli import _require_lane_approval
|
||
|
|
|
||
|
|
return _require_lane_approval(
|
||
|
|
cfg, entry, action, None, auth_args=auth_args
|
||
|
|
)
|