diff --git a/changelogs/fragments/templating-filter-generators.yml b/changelogs/fragments/templating-filter-generators.yml new file mode 100644 index 00000000000..e0fcfe9116c --- /dev/null +++ b/changelogs/fragments/templating-filter-generators.yml @@ -0,0 +1,3 @@ +bugfixes: + - templating - Ensure filter plugin result processing occurs under the correct call context. + (https://github.com/ansible/ansible/issues/85585) diff --git a/lib/ansible/_internal/_templating/_jinja_plugins.py b/lib/ansible/_internal/_templating/_jinja_plugins.py index 482dabfbb01..c1c5df0d87c 100644 --- a/lib/ansible/_internal/_templating/_jinja_plugins.py +++ b/lib/ansible/_internal/_templating/_jinja_plugins.py @@ -115,7 +115,13 @@ class JinjaPluginIntercept(c.MutableMapping): try: with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers): - return instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs)) + result = instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs)) + + if instance.plugin_type == 'filter': + # ensure list conversion occurs under the call context + result = _wrap_plugin_output(result) + + return result except MarkerError as ex: return ex.source except Exception as ex: @@ -156,7 +162,6 @@ class JinjaPluginIntercept(c.MutableMapping): @functools.wraps(instance.j2_function) def wrapper(*args, **kwargs) -> t.Any: result = self._invoke_plugin(instance, *args, **kwargs) - result = _wrap_plugin_output(result) return result diff --git a/test/units/_internal/templating/test_templar.py b/test/units/_internal/templating/test_templar.py index ed1c5eb40bb..c062d3f6b53 100644 --- a/test/units/_internal/templating/test_templar.py +++ b/test/units/_internal/templating/test_templar.py @@ -1078,3 +1078,18 @@ def test_marker_from_test_plugin() -> None: """Verify test plugins can raise MarkerError to return a Marker, and that no warnings or deprecations are emitted.""" with emits_warnings(deprecation_pattern=[], warning_pattern=[]): assert TemplateEngine(variables=dict(something=TRUST.tag("{{ nope }}"))).template(TRUST.tag("{{ (something is eq {}) is undefined }}")) + + +def test_filter_generator() -> None: + """Verify that filters which return a generator are converted to a list while under the filter's JinjaCallContext.""" + variables = dict( + foo=[ + dict(x=1, optional_var=0), + dict(x=2), + ], + bar=TRUST.tag("{{ foo | selectattr('optional_var', 'defined') }}"), + ) + + te = TemplateEngine(variables=variables) + te.template(TRUST.tag("{{ bar }}")) + te.template(TRUST.tag("{{ lookup('vars', 'bar') }}"))