Source code for constellation_utils.secrets

"""Public secrets API.

Top-level functions return frozen Pydantic models holding the resolved
credential values. Each function is cached per-process so repeated calls
are free.

Auth flows:
- Laptops: `op` CLI uses the desktop app's biometric-unlocked session.
- Rigs / unattended: same `op` CLI consumes ``OP_SERVICE_ACCOUNT_TOKEN``
  from the environment without a desktop app.
- Tests / CI: if ``R2_ACCESS_KEY_ID`` is already set in env, the env
  backend short-circuits the op CLI entirely.

Profile selection: ``CONSTELLATION_PROFILE=testing|production``
(default ``testing``).
"""

from __future__ import annotations

from functools import lru_cache

from constellation_utils.secrets._backends import (
    ConstellationAuthError,
    select_backend,
)
from constellation_utils.secrets._config import load_profile
from constellation_utils.secrets.models import CloudflareSecrets, R2Secrets

__all__ = [
    "r2",
    "cloudflare",
    "R2Secrets",
    "CloudflareSecrets",
    "ConstellationAuthError",
]


[docs] @lru_cache(maxsize=1) def r2() -> R2Secrets: """Return the R2 credentials for the current profile. Reads from 1Password via the ``op`` CLI by default, or from ``R2_*`` env vars in test/CI environments. Cached for the lifetime of the process. Raises ``ConstellationAuthError`` if no auth backend is available (e.g. ``op`` is not installed and ``R2_ACCESS_KEY_ID`` is not set). """ cfg = load_profile() backend = select_backend() return backend.read_r2(cfg["r2"])
[docs] @lru_cache(maxsize=1) def cloudflare() -> CloudflareSecrets: """Return the Cloudflare account API credentials for the current profile. Reads from 1Password via the ``op`` CLI by default, or from ``CLOUDFLARE_API_TOKEN`` + ``CLOUDFLARE_ACCOUNT_ID`` env vars in test/CI environments. Cached for the lifetime of the process. Raises ``ConstellationAuthError`` if the ``cloudflare:`` block is missing from the active profile's YAML, or if no auth backend is available. """ cfg = load_profile() if "cloudflare" not in cfg: raise ConstellationAuthError( "the active profile is missing a `cloudflare:` block.\n" " fix : add it to secrets.testing.yaml / secrets.production.yaml,\n" " mapping `api_token` and `account_id` to op:// URIs." ) backend = select_backend() return backend.read_cloudflare(cfg["cloudflare"])