Files
computer-playbook/cli/administration/inventory/validate/api.py
T
kevinveenbirkenbachandClaude Opus 5 acfc79db94 fix(merge): green the gate after the updater merge
Git conflicts only on lines both sides touched, so the feature/updater
merge dropped symbols and markers silently where one side alone had moved
them. Execution found them, not review.

- plugins/lookup/image.py bound get_entity_name while its test patches
  plugins.lookup.image.entity_name; an alias satisfies an importer but not
  mock.patch, which reads the module's own binding. The module now uses
  entity_name, the spelling 60+ call sites share. The alias stays for
  utils/update_check and plugins/filter/update, whose tests patch the old
  name.
- A download-source package resolves at its pinned URL, never in a
  distribution index, so the availability probe asked the wrong oracle and
  reported cursor absent. It now issues a HEAD against the declaration's
  URL. The CDN answers 403 rather than 404 for an object that is gone, so
  both count as absent.
- The validate suite still guarded the name "design", which moved into
  roles/web-svc-design/meta/services.yml in 86cefeff26.
- 193 meta/services.yml and meta/variants.yml declare why the guide edge
  is not a variant dimension of their role, and why docs rides each role's
  smallest round: the README replay is the costliest partner in the matrix.
  No new CI round comes out of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-23 19:43:54 +02:00

77 lines
2.4 KiB
Python

"""API credential validation: moved credential names and a missing Cloudflare token."""
from __future__ import annotations
from pathlib import Path
from utils.cache.api import declared_api
from .loaders import load_yaml_file
def legacy_api_keys(declared: set[str]) -> dict[str, str]:
"""Map the flat names older inventories gave API credentials to their ``API`` path.
Args:
declared: the variables core reads, see ``read_variables()``.
Returns:
``<PROVIDER>_<KEY>`` in upper case to ``API.<provider>.<key>``, without
the names core still reads as a variable of their own.
"""
legacy = {
f"{provider}_{key}".upper(): f"API.{provider}.{key}"
for provider, keys in declared_api().items()
for key in keys
}
return {name: path for name, path in legacy.items() if name not in declared}
def _cloudflare_token(data: dict) -> str:
api = data.get("API")
provider = api.get("cloudflare") if isinstance(api, dict) else None
token = provider.get("api_token") if isinstance(provider, dict) else None
return str(token or "").strip()
def _merged(paths: list[Path]) -> dict:
merged: dict = {}
for path in paths:
data = load_yaml_file(path)
if isinstance(data, dict):
merged.update(data)
return merged
def validate_api_keys(inv_dir) -> list[str]:
"""Report hosts that select Cloudflare DNS without a Cloudflare token.
Args:
inv_dir: the inventory directory.
Returns:
One message per host whose variables select ``DNS_PROVIDER: cloudflare``
without ``API.cloudflare.api_token``.
"""
root = Path(inv_dir)
group_all = _merged(
sorted(root.glob("group_vars/all.yml"))
+ sorted(root.glob("group_vars/all/*.yml"))
)
hosts = [(path, [path]) for path in sorted(root.glob("host_vars/*.yml"))]
hosts += [
(path, sorted(path.glob("*.yml")))
for path in sorted(root.glob("host_vars/*"))
if path.is_dir()
]
errs: list[str] = []
for path, files in hosts or [(root / "group_vars", [])]:
effective = {**group_all, **_merged(files)}
if effective.get("DNS_PROVIDER") == "cloudflare" and not _cloudflare_token(
effective
):
errs.append(
f"{path}: DNS_PROVIDER is cloudflare, but API.cloudflare.api_token is empty"
)
return errs