mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
`utils.api` shadowed `ansible.module_utils.api` for anything that reached the top-level `utils` package first, which the ai-agent-employees merge made reachable. The module caches provider declarations, so it belongs beside the other caches rather than at the root. The three importers and the two documentation references move with it; nothing else names the old path. Verified: `from utils.cache.api import declared_api, provider_enabled, resolve_api` resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
3.8 KiB
Python
113 lines
3.8 KiB
Python
"""Proprietary API credential SPOT, read from group_vars/all/18_api.yml.
|
|
|
|
``API`` is a plain mapping, so an inventory that configures three providers
|
|
replaces the whole declaration instead of merging into it, and every provider it
|
|
does not name disappears. Resolving a hidden provider then raises, and a raising
|
|
lookup inside a value the applications renderer is templating leaves the raw
|
|
``{{ ... }}`` text in place: the consumer stores template text, and ``| bool``
|
|
coerces it to False with a deprecation warning that fails the deploy on the
|
|
warning gate.
|
|
|
|
Merging the scope's override over the declared defaults keeps every declared
|
|
provider resolvable, so an override narrows values, never the provider set.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from utils import PROJECT_ROOT
|
|
from utils.cache.base import _deep_merge
|
|
from utils.cache.yaml import load_yaml_any
|
|
|
|
_API_SPOT_FILE = str(PROJECT_ROOT / "group_vars" / "all" / "18_api.yml")
|
|
|
|
|
|
def declared_api() -> dict[str, Any]:
|
|
"""The ``API`` mapping as declared in the group_vars SPOT.
|
|
|
|
Returns:
|
|
Provider name to credential mapping, every value a plain string.
|
|
|
|
Raises:
|
|
TypeError: the SPOT does not declare ``API`` as a mapping.
|
|
"""
|
|
document = load_yaml_any(_API_SPOT_FILE, default_if_missing={}) or {}
|
|
declared = document.get("API") if isinstance(document, Mapping) else None
|
|
if not isinstance(declared, Mapping):
|
|
raise TypeError(f"{_API_SPOT_FILE} does not declare 'API' as a mapping.")
|
|
return dict(declared)
|
|
|
|
|
|
def resolve_api(
|
|
variables: Mapping[str, Any] | None, templar: Any = None
|
|
) -> dict[str, Any]:
|
|
"""The effective ``API`` mapping for a render scope.
|
|
|
|
Args:
|
|
variables: the scope's variables; its ``API`` entry overrides the SPOT.
|
|
templar: optional templar; renders the override and the merged values,
|
|
so an inventory may source a credential from an env lookup.
|
|
|
|
Returns:
|
|
The SPOT declaration with the scope's override merged over it.
|
|
|
|
Raises:
|
|
TypeError: the scope's ``API`` is present but not a mapping.
|
|
"""
|
|
declared = declared_api()
|
|
override: Any = (variables or {}).get("API")
|
|
if override is not None and not isinstance(override, Mapping) and templar:
|
|
override = _templated(templar, override)
|
|
if override is None:
|
|
return declared
|
|
if not isinstance(override, Mapping):
|
|
raise TypeError("'API' is defined but is not a mapping.")
|
|
merged = _deep_merge(declared, dict(override))
|
|
if templar is None:
|
|
return merged
|
|
rendered = _templated(templar, merged)
|
|
return dict(rendered) if isinstance(rendered, Mapping) else merged
|
|
|
|
|
|
def _templated(templar: Any, value: Any) -> Any:
|
|
from ansible.errors import AnsibleError
|
|
|
|
try:
|
|
return templar.template(value, fail_on_undefined=False)
|
|
except TypeError:
|
|
pass
|
|
except (AnsibleError, ValueError):
|
|
return value
|
|
|
|
try:
|
|
return templar.template(value)
|
|
except (AnsibleError, TypeError, ValueError):
|
|
return value
|
|
|
|
|
|
def provider_enabled(api: Mapping[str, Any], provider: str) -> bool:
|
|
"""Whether every credential of a provider carries a value.
|
|
|
|
Args:
|
|
api: an effective ``API`` mapping, as returned by ``resolve_api``.
|
|
provider: the provider name, e.g. ``github``.
|
|
|
|
Returns:
|
|
True when the provider declares at least one credential and none of
|
|
them is empty.
|
|
|
|
Raises:
|
|
KeyError: the provider is not declared in the SPOT.
|
|
"""
|
|
credentials = api.get(provider)
|
|
if not isinstance(credentials, Mapping):
|
|
raise KeyError(
|
|
f"unknown API provider {provider!r}; declare it under 'API' in "
|
|
f"{_API_SPOT_FILE}."
|
|
)
|
|
if not credentials:
|
|
return False
|
|
return all(str(value).strip() for value in credentials.values())
|