mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(current_play_redirect_domains): template DOMAIN_PRIMARY/HOMEPAGE defensively
The plugin's previous
domain_primary = str(vars_.get("DOMAIN_PRIMARY") or "")
domain_homepage = str(vars_.get("DOMAIN_HOMEPAGE") or "")
read inventory values that may carry Ansible 2.18+'s
``_AnsibleTaggedStr`` with the ``TrustedAsTemplate`` tag — exactly
the shape ``inventories/development/default.yml:20`` produces with
DOMAIN_PRIMARY: "{{ lookup('env', 'INFINITO_DOMAIN') }}"
A bare ``str(value)`` casts the tag away and leaves the literal Jinja
text in the plugin output. The output then flows into
``CURRENT_PLAY_REDIRECT_DOMAINS`` via the set_fact in
``tasks/stages/01_constructor.yml`` and lands in
``roles/web-opt-rdr-domains/tasks/main.yml`` loop items as
item={'source': "{{ lookup('env','INFINITO_DOMAIN') }}", 'target': "{{ … }}"}
The ``lookup('tls', domain, 'enabled')`` call in
``roles/web-opt-rdr-domains/tasks/redirect-domain.yml:5`` then
receives the literal Jinja string and raises
The lookup plugin 'tls' failed: tls: application_id
'{{ lookup('env', 'INFINITO_DOMAIN') }}' not found in domains mapping
(job 77797371504 on run 26428080957). That CI ran on SHA d1410c0be,
which already shipped this plugin — so the upstream replacement of
the inline two-stage set_fact merges with the lookup plugin did not
fix the underlying trust-tag-strip bug; it just relocated it from
Jinja to Python.
Add ``_resolve_str`` that runs the value through ``self._templar``
before string-coercion. Idempotent for already-resolved strings,
exception-safe (falls back to ``str(raw)`` if the templar is
unavailable, e.g. on hosts without rendered vars). Both
``DOMAIN_PRIMARY`` and ``DOMAIN_HOMEPAGE`` route through it.
Defense-in-depth with the existing ``plugins/lookup/tls.py``
defensive templating (commit f32266d53): even if a future code path
hands ``lookup('tls', …)`` an untemplated string, the tls plugin
itself now templates the first term. Either layer alone suffices for
the rdr-domains failure; both together guarantee the symptom cannot
re-surface from a single regression elsewhere.
Coverage: two regression tests in
``tests/unit/plugins/lookup/test_current_play_redirect_domains.py``
— ``test_jinja_tagged_domain_primary_is_templated`` proves the
templar call happens and produces the resolved domain, and
``test_templar_failure_falls_back_to_raw_value`` proves a raising
templar does not crash the plugin. The 10 existing tests stay green.
``make test`` green across all 5 targets.
Class follow-up: 11 further lookup plugins read inventory values
without ``templar.template`` first (see git log for ``vars_.get``
call sites). Not in scope for this fix; logged for a separate
audit pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
222cc4015b
commit
7b9cb56176
2 files changed
+77
-2
No files matched your search
@@ -141,6 +141,20 @@ class LookupModule(LookupBase):
|
||||
result = plugin.run([], variables=variables)
|
||||
return result[0] if result else None
|
||||
|
||||
def _resolve_str(self, value: Any) -> str:
|
||||
"""Template a possibly Jinja-tagged inventory value through
|
||||
``self._templar`` before string-coercion; bare ``str()`` would
|
||||
strip the ``TrustedAsTemplate`` tag on Ansible 2.18+."""
|
||||
if value is None:
|
||||
return ""
|
||||
templar = getattr(self, "_templar", None)
|
||||
if templar is not None:
|
||||
try:
|
||||
value = templar.template(value)
|
||||
except Exception:
|
||||
pass
|
||||
return str(value)
|
||||
|
||||
def run(
|
||||
self,
|
||||
terms: list[Any] | None,
|
||||
@@ -149,8 +163,8 @@ class LookupModule(LookupBase):
|
||||
) -> list[list[dict[str, str]]]:
|
||||
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
|
||||
|
||||
domain_primary = str(vars_.get("DOMAIN_PRIMARY") or "")
|
||||
domain_homepage = str(vars_.get("DOMAIN_HOMEPAGE") or "")
|
||||
domain_primary = self._resolve_str(vars_.get("DOMAIN_PRIMARY"))
|
||||
domain_homepage = self._resolve_str(vars_.get("DOMAIN_HOMEPAGE"))
|
||||
auto_build_aliases = bool(vars_.get("AUTO_BUILD_ALIASES", False))
|
||||
|
||||
user_mappings = vars_.get("redirect_domain_mappings") or []
|
||||
|
||||
@@ -190,6 +190,67 @@ class CurrentPlayRedirectDomainsLookupTests(unittest.TestCase):
|
||||
[{"source": "infinito.example", "target": "infinito.nexus"}],
|
||||
)
|
||||
|
||||
def test_jinja_tagged_domain_primary_is_templated(self):
|
||||
class _ResolvingTemplar:
|
||||
def __init__(self, mapping):
|
||||
self._mapping = mapping
|
||||
self.available_variables = {}
|
||||
self.calls = []
|
||||
|
||||
def template(self, value):
|
||||
self.calls.append(value)
|
||||
return self._mapping.get(value, value)
|
||||
|
||||
lm = LookupModule()
|
||||
lm._templar = _ResolvingTemplar(
|
||||
{
|
||||
"{{ lookup('env','INFINITO_DOMAIN') }}": "infinito.example",
|
||||
"{{ lookup('domain','web-app-dashboard') }}": "infinito.nexus",
|
||||
}
|
||||
)
|
||||
lm._loader = None
|
||||
variables = {
|
||||
"DOMAIN_PRIMARY": "{{ lookup('env','INFINITO_DOMAIN') }}",
|
||||
"DOMAIN_HOMEPAGE": "{{ lookup('domain','web-app-dashboard') }}",
|
||||
}
|
||||
with self._patch_lookups(
|
||||
deployed=["web-opt-rdr-domains"], current_play_apps={}
|
||||
):
|
||||
result = lm.run(terms=[], variables=variables)[0]
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
[{"source": "infinito.example", "target": "infinito.nexus"}],
|
||||
)
|
||||
self.assertIn(
|
||||
"{{ lookup('env','INFINITO_DOMAIN') }}",
|
||||
lm._templar.calls,
|
||||
)
|
||||
|
||||
def test_templar_failure_falls_back_to_raw_value(self):
|
||||
class _FailingTemplar:
|
||||
def __init__(self):
|
||||
self.available_variables = {}
|
||||
|
||||
def template(self, value):
|
||||
raise RuntimeError("templar unavailable")
|
||||
|
||||
lm = LookupModule()
|
||||
lm._templar = _FailingTemplar()
|
||||
lm._loader = None
|
||||
variables = {
|
||||
"DOMAIN_PRIMARY": "infinito.example",
|
||||
"DOMAIN_HOMEPAGE": "infinito.nexus",
|
||||
}
|
||||
with self._patch_lookups(
|
||||
deployed=["web-opt-rdr-domains"], current_play_apps={}
|
||||
):
|
||||
result = lm.run(terms=[], variables=variables)[0]
|
||||
self.assertEqual(
|
||||
result,
|
||||
[{"source": "infinito.example", "target": "infinito.nexus"}],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
Reference in new issue
Block a user