mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
API is a plain mapping, so an inventory that configures three providers replaces the whole declaration from group_vars/all/18_api.yml instead of merging into it. inventories/development/default.yml declares openai, anthropic and openrouter, so github, google and jira did not exist in CI at all and lookup('api', 'github.client_id') raised.
A raising lookup inside a value the applications renderer is templating does not fail the render: _templar_render_best_effort returns the string untouched and, for a value carrying a non-env lookup(, refuses the fallback as well. The raw template text reached plugin_value.enabled | bool, which coerced it to False and emitted the deprecation warning that fails the deploy gate. Run 34893363518, job 104153194109 lost web-app-nextcloud to exactly that, for three integrations at once. The configvalue entries would have stored template text as the client id.
utils/api.py merges the scope's override over the declaration, so an override narrows values and never the provider set, and holds the enable rule: every credential a provider declares must carry a value. The api lookup reads through it; the new api_enabled lookup gates on it and returns a real boolean instead of a string a filter has to coerce.
The same class sat in group_vars/all/16_ai.yml and roles/svc-ai-litellm/meta/secrets.yml as bare API.<provider>.<key> dereferences; both now go through the lookups.
Two lints keep it shut. test_addon_enabled_renders_to_bool renders the whole applications map against a scope with API blanked, the most hostile override an inventory may legally write, and requires every addon gate to come out a boolean. test_api_accessed_via_lookup forbids a bare API dereference in Jinja under roles/, group_vars/ and inventories/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
41 lines
1.5 KiB
Python
41 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.api import provider_enabled, resolve_api
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
"""
|
|
lookup('api_enabled', '<provider>') e.g. lookup('api_enabled', 'github')
|
|
|
|
True when every credential the provider declares in group_vars/all/18_api.yml
|
|
carries a value in the effective ``API`` mapping. Integrations that depend on
|
|
a proprietary provider gate on this instead of spelling the emptiness test
|
|
out per call site, so an added credential key extends the gate everywhere and
|
|
the result is a real boolean rather than a string a filter has to coerce.
|
|
"""
|
|
|
|
def run(self, terms, variables: dict[str, Any] | None = None, **kwargs):
|
|
if not terms or len(terms) != 1:
|
|
raise AnsibleError(
|
|
"lookup('api_enabled') takes exactly one term: '<provider>' "
|
|
"(e.g. 'github')."
|
|
)
|
|
|
|
templar = getattr(self, "_templar", None)
|
|
variables = variables or getattr(templar, "available_variables", {}) or {}
|
|
|
|
provider = str(terms[0]).strip()
|
|
if not provider:
|
|
raise AnsibleError("lookup('api_enabled'): empty provider name.")
|
|
|
|
try:
|
|
api = resolve_api(variables, templar=templar)
|
|
return [provider_enabled(api, provider)]
|
|
except (KeyError, TypeError) as error:
|
|
raise AnsibleError(f"lookup('api_enabled'): {error}") from error
|