Allow for lazy evaluation of Jinja2 expressions (#56116)

pull/77544/head
Martin Krizek 3 years ago committed by GitHub
parent f2ab920822
commit cbe42bff7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
breaking_changes:
- Allow for lazy evaluation of Jinja2 expressions (https://github.com/ansible/ansible/issues/56017)

@ -19,7 +19,19 @@ This document is part of a collection on porting. The complete list of porting g
Playbook Playbook
======== ========
No notable changes * Variables are now evaluated lazily; only when they are actually used. For example, in ansible-core 2.14 an expression ``{{ defined_variable or undefined_variable }}`` does not fail on ``undefined_variable`` if the first part of ``or`` is evaluated to ``True`` as it is not needed to evaluate the second part. One particular case of a change in behavior to note is the task below which uses the ``undefined`` test. Prior to version 2.14 this would result in a fatal error trying to access the undefined value in the dictionary. In 2.14 the assertion passes as the dictionary is evaluated as undefined through one of its undefined values:
.. code-block:: yaml
- assert:
that:
- some_defined_dict_with_undefined_values is undefined
vars:
dict_value: 1
some_defined_dict_with_undefined_values:
key1: value1
key2: '{{ dict_value }}'
key3: '{{ undefined_dict_value }}'
Command Line Command Line

@ -97,7 +97,15 @@ class AnsibleJ2Vars(Mapping):
try: try:
value = self._templar.template(variable) value = self._templar.template(variable)
except AnsibleUndefinedVariable as e: except AnsibleUndefinedVariable as e:
raise AnsibleUndefinedVariable("%s: %s" % (to_native(variable), e.message)) # Instead of failing here prematurely, return an Undefined
# object which fails only after its first usage allowing us to
# do lazy evaluation and passing it into filters/tests that
# operate on such objects.
return self._templar.environment.undefined(
hint=f"{variable}: {e.message}",
name=varname,
exc=AnsibleUndefinedVariable,
)
except Exception as e: except Exception as e:
msg = getattr(e, 'message', None) or to_native(e) msg = getattr(e, 'message', None) or to_native(e)
raise AnsibleError("An unhandled exception occurred while templating '%s'. " raise AnsibleError("An unhandled exception occurred while templating '%s'. "

@ -0,0 +1,24 @@
- hosts: testhost
gather_facts: false
vars:
deep_undefined: "{{ nested_undefined_variable }}"
tasks:
- name: These do not throw an error, deep_undefined is just evaluated to undefined, since 2.13
assert:
that:
- lazy_eval or deep_undefined
- deep_undefined is undefined
- deep_undefined|default('defaulted') == 'defaulted'
vars:
lazy_eval: true
- name: EXPECTED FAILURE actually using deep_undefined fails
debug:
msg: "{{ deep_undefined }}"
ignore_errors: true
register: res
- assert:
that:
- res.failed
- res.msg is contains("'nested_undefined_variable' is undefined")

@ -40,3 +40,5 @@ ansible-playbook unsafe.yml -v "$@"
# ensure Jinja2 overrides from a template are used # ensure Jinja2 overrides from a template are used
ansible-playbook in_template_overrides.yml -v "$@" ansible-playbook in_template_overrides.yml -v "$@"
ansible-playbook lazy_eval.yml -i ../../inventory -v "$@"

@ -121,19 +121,6 @@ class TestConditional(unittest.TestCase):
self._eval_con, self._eval_con,
when, variables) when, variables)
def test_dict_undefined_values_is_defined(self):
variables = {'dict_value': 1,
'some_defined_dict_with_undefined_values': {'key1': 'value1',
'key2': '{{ dict_value }}',
'key3': '{{ undefined_dict_value }}'
}}
when = [u"some_defined_dict_with_undefined_values is defined"]
self.assertRaisesRegex(errors.AnsibleError,
"The conditional check 'some_defined_dict_with_undefined_values is defined' failed.",
self._eval_con,
when, variables)
def test_is_defined(self): def test_is_defined(self):
variables = {'some_defined_thing': True} variables = {'some_defined_thing': True}
when = [u"some_defined_thing is defined"] when = [u"some_defined_thing is defined"]

Loading…
Cancel
Save