mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 13:17:07 +00:00
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>
77 lines
2.4 KiB
Python
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
|