Fix when evaluation on Native Jinja and Python 3.10 (#75202) (#75380)

* 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>
(cherry picked from commit 58e38044fe)
pull/74257/head
Martin Krizek 3 years ago committed by GitHub
parent da1baaf485
commit 4078765379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- Fix ``when`` evaluation on Native Jinja and Python 3.10.

@ -28,7 +28,7 @@ from jinja2.exceptions import UndefinedError
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleUndefinedVariable from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils.six import text_type 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.playbook.attribute import FieldAttribute
from ansible.utils.display import Display from ansible.utils.display import Display
@ -192,8 +192,15 @@ class Conditional:
raise AnsibleError("Invalid conditional detected: %s" % to_native(e)) raise AnsibleError("Invalid conditional detected: %s" % to_native(e))
# and finally we generate and template the presented string and look at the resulting string # 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 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": if val == "True":
return True return True
elif val == "False": elif val == "False":

@ -5,24 +5,45 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import importlib
import sys
import pytest import pytest
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleUndefinedVariable from ansible.errors import AnsibleUndefinedVariable
from ansible.playbook.conditional import Conditional
# 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 units.mock.loader import DictDataLoader 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 # https://github.com/ansible/ansible/issues/52158
def test_undefined_variable(): def test_undefined_variable(native_template_mod):
fake_loader = DictDataLoader({}) fake_loader = DictDataLoader({})
variables = {} 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): with pytest.raises(AnsibleUndefinedVariable):
templar.template("{{ missing }}") 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)

Loading…
Cancel
Save