summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Krizek <martin.krizek@gmail.com>2021-07-30 17:27:49 +0200
committerGitHub <noreply@github.com>2021-07-30 17:27:49 +0200
commit58e38044fe9c8fc7e06ffd61a93f88db27536af1 (patch)
treed8a15aa16fc06a429ea55ea1a8c328cafdec41c6
parentansible-test - add RHEL 8.4 as a remote (#75362) (diff)
downloadansible-58e38044fe9c8fc7e06ffd61a93f88db27536af1.tar.xz
ansible-58e38044fe9c8fc7e06ffd61a93f88db27536af1.zip
Fix when evaluation on Native Jinja and Python 3.10 (#75202)
* Fix when evaluation on Native Jinja and Python 3.10 * Add unit test * Add explaining comment * Enable jinja2_native before tests Co-Authored-By: Matt Martz <matt@sivel.net> * Sanity * Return native template module instead of modifying globals Co-authored-by: Matt Martz <matt@sivel.net>
-rw-r--r--changelogs/fragments/when-eval-native-py310.yml2
-rw-r--r--lib/ansible/playbook/conditional.py11
-rw-r--r--test/units/template/test_native_concat.py35
3 files changed, 39 insertions, 9 deletions
diff --git a/changelogs/fragments/when-eval-native-py310.yml b/changelogs/fragments/when-eval-native-py310.yml
new file mode 100644
index 0000000000..f90b948add
--- /dev/null
+++ b/changelogs/fragments/when-eval-native-py310.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - Fix ``when`` evaluation on Native Jinja and Python 3.10.
diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
index 293013a281..7afcb41c06 100644
--- a/lib/ansible/playbook/conditional.py
+++ b/lib/ansible/playbook/conditional.py
@@ -28,7 +28,7 @@ from jinja2.exceptions import UndefinedError
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils.six import text_type
-from ansible.module_utils._text import to_native
+from ansible.module_utils._text import to_native, to_text
from ansible.playbook.attribute import FieldAttribute
from ansible.utils.display import Display
@@ -181,8 +181,15 @@ class Conditional:
raise AnsibleError("Invalid conditional detected: %s" % to_native(e))
# and finally we generate and template the presented string and look at the resulting string
+ # NOTE The spaces around True and False are intentional to short-circuit safe_eval and avoid
+ # its expensive calls.
presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional
- val = templar.template(presented, disable_lookups=disable_lookups).strip()
+ # NOTE Convert the result to text to account for both native and non-native jinja.
+ # NOTE The templated result of `presented` is string on native jinja as well prior to Python 3.10.
+ # ast.literal_eval on Python 3.10 removes leading whitespaces so " True " becomes bool True
+ # as opposed to Python 3.9 and lower where the same would result in IndentationError and
+ # string " True " would be returned by Templar.
+ val = to_text(templar.template(presented, disable_lookups=disable_lookups)).strip()
if val == "True":
return True
elif val == "False":
diff --git a/test/units/template/test_native_concat.py b/test/units/template/test_native_concat.py
index db85a73b92..4164bc4578 100644
--- a/test/units/template/test_native_concat.py
+++ b/test/units/template/test_native_concat.py
@@ -5,24 +5,45 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import importlib
+import sys
+
import pytest
from ansible import constants as C
from ansible.errors import AnsibleUndefinedVariable
-
-# need to mock DEFAULT_JINJA2_NATIVE here so native modules are imported
-# correctly within the template module
-C.DEFAULT_JINJA2_NATIVE = True
-from ansible.template import Templar
+from ansible.playbook.conditional import Conditional
from units.mock.loader import DictDataLoader
+@pytest.fixture
+def native_template_mod(monkeypatch):
+ monkeypatch.delitem(sys.modules, 'ansible.template')
+ monkeypatch.setattr(C, 'DEFAULT_JINJA2_NATIVE', True)
+ return importlib.import_module('ansible.template')
+
+
# https://github.com/ansible/ansible/issues/52158
-def test_undefined_variable():
+def test_undefined_variable(native_template_mod):
fake_loader = DictDataLoader({})
variables = {}
- templar = Templar(loader=fake_loader, variables=variables)
+ templar = native_template_mod.Templar(loader=fake_loader, variables=variables)
+ assert isinstance(templar.environment, native_template_mod.AnsibleNativeEnvironment)
with pytest.raises(AnsibleUndefinedVariable):
templar.template("{{ missing }}")
+
+
+def test_cond_eval(native_template_mod):
+ fake_loader = DictDataLoader({})
+ # True must be stored in a variable to trigger templating. Using True
+ # directly would be caught by optimization for bools to short-circuit
+ # templating.
+ variables = {"foo": True}
+ templar = native_template_mod.Templar(loader=fake_loader, variables=variables)
+ assert isinstance(templar.environment, native_template_mod.AnsibleNativeEnvironment)
+
+ cond = Conditional(loader=fake_loader)
+ cond.when = ["foo"]
+ assert cond.evaluate_conditional(templar, variables)