From 3518d48146b3ecda6f8cfc6c33922aa9d492ad4f Mon Sep 17 00:00:00 2001 From: Matt Davis <6775756+nitzmahone@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:42:15 -0700 Subject: [PATCH] ensure undefined marker access on Jinja getattr->getitem fallback (#85688) Co-authored-by: Matt Clay --- changelogs/fragments/getattr_marker_access.yml | 4 ++++ lib/ansible/_internal/_templating/_jinja_bits.py | 2 +- .../_internal/templating/test_jinja_bits.py | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/getattr_marker_access.yml diff --git a/changelogs/fragments/getattr_marker_access.yml b/changelogs/fragments/getattr_marker_access.yml new file mode 100644 index 00000000000..d23ec5f9341 --- /dev/null +++ b/changelogs/fragments/getattr_marker_access.yml @@ -0,0 +1,4 @@ +bugfixes: + - templating - Undefined marker values sourced from the Jinja ``getattr->getitem`` fallback are now accessed correctly, + raising AnsibleUndefinedVariable for user plugins that do not understand markers. + Previously, these values were erroneously returned to user plugin code that had not opted in to marker acceptance. diff --git a/lib/ansible/_internal/_templating/_jinja_bits.py b/lib/ansible/_internal/_templating/_jinja_bits.py index 6c2166fa3c3..34e777bac2f 100644 --- a/lib/ansible/_internal/_templating/_jinja_bits.py +++ b/lib/ansible/_internal/_templating/_jinja_bits.py @@ -811,7 +811,7 @@ class AnsibleEnvironment(SandboxedEnvironment): try: value = obj[attribute] except (TypeError, LookupError): - return self.undefined(obj=obj, name=attribute) if is_safe else self.unsafe_undefined(obj, attribute) + value = self.undefined(obj=obj, name=attribute) if is_safe else self.unsafe_undefined(obj, attribute) AnsibleAccessContext.current().access(value) diff --git a/test/units/_internal/templating/test_jinja_bits.py b/test/units/_internal/templating/test_jinja_bits.py index ad32166da70..35d24c536c7 100644 --- a/test/units/_internal/templating/test_jinja_bits.py +++ b/test/units/_internal/templating/test_jinja_bits.py @@ -12,7 +12,7 @@ import pytest_mock from ansible.errors import AnsibleUndefinedVariable, AnsibleTemplateError from ansible._internal._templating._errors import AnsibleTemplatePluginRuntimeError from ansible.module_utils._internal._datatag import AnsibleTaggedObject -from ansible._internal._templating._jinja_common import CapturedExceptionMarker, MarkerError, Marker, UndefinedMarker +from ansible._internal._templating._jinja_common import CapturedExceptionMarker, MarkerError, Marker, UndefinedMarker, JinjaCallContext from ansible._internal._templating._utils import TemplateContext from ansible._internal._datatag._tags import TrustedAsTemplate from ansible._internal._templating._jinja_bits import (AnsibleEnvironment, TemplateOverrides, _TEMPLATE_OVERRIDE_FIELD_NAMES, defer_template_error, @@ -443,3 +443,17 @@ def test_mutation_methods(template: str, result: object) -> None: This feature may be deprecated and removed in a future release by using Jinja's ImmutableSandboxedEnvironment. """ assert TemplateEngine().template(TRUST.tag(template)) == result + + +@pytest.mark.parametrize("template", ( + '{{ adict["bogus"] | default("ok") }}', + '{{ adict.bogus | default("ok") }}', +)) +def test_marker_access_getattr_and_getitem(template: str) -> None: + """Ensure that getattr and getitem always access markers.""" + # the absence of a JinjaCallContext should cause the access done by getattr and getitem not to trip when a marker is encountered + assert TemplateEngine(variables=dict(adict={})).template(TRUST.tag(template)) == "ok" + + with pytest.raises(AnsibleUndefinedVariable): + with JinjaCallContext(accept_lazy_markers=False): # the access done by getattr and getitem should immediately trip when a marker is encountered + TemplateEngine(variables=dict(adict={})).template(TRUST.tag(template))