1030 lines
44 KiB
Diff
1030 lines
44 KiB
Diff
From f5cb50f79af310b917e6932a0c0d8e9a73261b7f Mon Sep 17 00:00:00 2001
|
|
From: Matt Martz <matt@sivel.net>
|
|
Date: Thu, 18 Jan 2024 17:17:23 -0600
|
|
Subject: [PATCH 2/2] [stable-2.14] Ensure ANSIBLE_NO_LOG is respected
|
|
(CVE-2024-0690) (#82565) (#82568)
|
|
|
|
Origin:https://build.opensuse.org/projects/SUSE:SLE-15-SP3:Update/packages/ansible/files/0002-Ensure-ANSIBLE_NO_LOG-is-respected-CVE-2024-0690-825.patch?expand=1
|
|
https://github.com/ansible/ansible/pull/82566
|
|
https://github.com/ansible/ansible/pull/68560
|
|
https://github.com/ansible/ansible/pull/68014
|
|
https://github.com/ansible/ansible/pull/71313
|
|
https://github.com/ansible/ansible/pull/60513
|
|
|
|
(cherry picked from commit 6935c8e)
|
|
|
|
Force template module to use non-native Jinja2 (#68560)
|
|
|
|
Fixes #46169
|
|
|
|
Auto unroll generators produced by jinja filters (#68014)
|
|
|
|
* Auto unroll generators produced by jinja filters
|
|
|
|
* Unroll for native in finalize
|
|
|
|
* Fix indentation
|
|
|
|
Co-authored-by: Sam Doran <sdoran@redhat.com>
|
|
|
|
* Add changelog fragment
|
|
|
|
* ci_complete
|
|
|
|
* Always unroll regardless of jinja2
|
|
|
|
* ci_complete
|
|
|
|
Co-authored-by: Sam Doran <sdoran@redhat.com>
|
|
|
|
Skip literal_eval for string filters results in native jinja. (#70988) (#71313)
|
|
|
|
Fixes #70831
|
|
|
|
(cherry picked from commit b66d66027ece03f3f0a3fdb5fd6b8213965a2f1d)
|
|
|
|
Introduce context manager for temporary templar context changes (#60513)
|
|
|
|
* Introduce context manager for temporary templar context changes. Fixes #60106
|
|
|
|
* Rename and docstring
|
|
|
|
* Make set_temporary_context more generic, don't hardcode each thing you can set, apply to template action too
|
|
|
|
* not None
|
|
|
|
* linting fix
|
|
|
|
* Ignore invalid attrs
|
|
|
|
* Catch the right things, loop the right things
|
|
|
|
* Use set_temporary_context in a few extra action plugins
|
|
---
|
|
.../46169-non-native-template-module.yml | 2 +
|
|
.../60106-templar-contextmanager.yml | 4 +
|
|
.../68014-auto-unroll-jinja2-generators.yml | 3 +
|
|
...iteral_eval-string-filter-native-jinja.yml | 2 +
|
|
changelogs/fragments/cve-2024-0690.yml | 2 +
|
|
lib/ansible/config/base.yml | 2 +-
|
|
lib/ansible/playbook/base.py | 2 +-
|
|
lib/ansible/playbook/conditional.py | 4 +-
|
|
lib/ansible/playbook/play_context.py | 4 -
|
|
lib/ansible/plugins/action/ce_template.py | 4 +-
|
|
lib/ansible/plugins/action/network.py | 4 +-
|
|
lib/ansible/plugins/action/template.py | 32 ++-
|
|
lib/ansible/plugins/lookup/template.py | 50 ++--
|
|
lib/ansible/template/__init__.py | 227 ++++++++++++++++--
|
|
lib/ansible/template/native_helpers.py | 39 +++
|
|
lib/ansible/utils/native_jinja.py | 13 +
|
|
lib/ansible/utils/unsafe_proxy.py | 7 +
|
|
.../jinja2_native_types/test_casting.yml | 7 +
|
|
.../jinja2_native_types/test_dunder.yml | 2 +-
|
|
.../targets/no_log/no_log_config.yml | 13 +
|
|
test/integration/targets/no_log/runme.sh | 5 +
|
|
.../template_jinja2_non_native/46169.yml | 32 +++
|
|
.../template_jinja2_non_native/aliases | 1 +
|
|
.../template_jinja2_non_native/runme.sh | 7 +
|
|
.../templates/46169.json.j2 | 3 +
|
|
25 files changed, 398 insertions(+), 73 deletions(-)
|
|
create mode 100644 changelogs/fragments/46169-non-native-template-module.yml
|
|
create mode 100644 changelogs/fragments/60106-templar-contextmanager.yml
|
|
create mode 100644 changelogs/fragments/68014-auto-unroll-jinja2-generators.yml
|
|
create mode 100644 changelogs/fragments/70831-skip-literal_eval-string-filter-native-jinja.yml
|
|
create mode 100644 changelogs/fragments/cve-2024-0690.yml
|
|
create mode 100644 lib/ansible/utils/native_jinja.py
|
|
create mode 100644 test/integration/targets/no_log/no_log_config.yml
|
|
create mode 100644 test/integration/targets/template_jinja2_non_native/46169.yml
|
|
create mode 100644 test/integration/targets/template_jinja2_non_native/aliases
|
|
create mode 100755 test/integration/targets/template_jinja2_non_native/runme.sh
|
|
create mode 100644 test/integration/targets/template_jinja2_non_native/templates/46169.json.j2
|
|
|
|
diff --git a/changelogs/fragments/46169-non-native-template-module.yml b/changelogs/fragments/46169-non-native-template-module.yml
|
|
new file mode 100644
|
|
index 0000000000..7d004a6296
|
|
--- /dev/null
|
|
+++ b/changelogs/fragments/46169-non-native-template-module.yml
|
|
@@ -0,0 +1,2 @@
|
|
+minor_changes:
|
|
+ - Force the template module to use non-native Jinja2 (https://github.com/ansible/ansible/issues/46169)
|
|
diff --git a/changelogs/fragments/60106-templar-contextmanager.yml b/changelogs/fragments/60106-templar-contextmanager.yml
|
|
new file mode 100644
|
|
index 0000000000..45afc1544a
|
|
--- /dev/null
|
|
+++ b/changelogs/fragments/60106-templar-contextmanager.yml
|
|
@@ -0,0 +1,4 @@
|
|
+bugfixes:
|
|
+- template lookup - ensure changes to the templar in the lookup, do not
|
|
+ affect the templar context outside of the lookup
|
|
+ (https://github.com/ansible/ansible/issues/60106)
|
|
diff --git a/changelogs/fragments/68014-auto-unroll-jinja2-generators.yml b/changelogs/fragments/68014-auto-unroll-jinja2-generators.yml
|
|
new file mode 100644
|
|
index 0000000000..211d2fd665
|
|
--- /dev/null
|
|
+++ b/changelogs/fragments/68014-auto-unroll-jinja2-generators.yml
|
|
@@ -0,0 +1,3 @@
|
|
+minor_changes:
|
|
+- Templating - Add support to auto unroll generators produced by jinja2 filters, to prevent the need of explicit use of ``|list``
|
|
+ (https://github.com/ansible/ansible/pull/68014)
|
|
diff --git a/changelogs/fragments/70831-skip-literal_eval-string-filter-native-jinja.yml b/changelogs/fragments/70831-skip-literal_eval-string-filter-native-jinja.yml
|
|
new file mode 100644
|
|
index 0000000000..40b426e50b
|
|
--- /dev/null
|
|
+++ b/changelogs/fragments/70831-skip-literal_eval-string-filter-native-jinja.yml
|
|
@@ -0,0 +1,2 @@
|
|
+bugfixes:
|
|
+ - Skip literal_eval for string filters results in native jinja. (https://github.com/ansible/ansible/issues/70831)
|
|
diff --git a/changelogs/fragments/cve-2024-0690.yml b/changelogs/fragments/cve-2024-0690.yml
|
|
new file mode 100644
|
|
index 0000000000..0e030d8886
|
|
--- /dev/null
|
|
+++ b/changelogs/fragments/cve-2024-0690.yml
|
|
@@ -0,0 +1,2 @@
|
|
+security_fixes:
|
|
+- ANSIBLE_NO_LOG - Address issue where ANSIBLE_NO_LOG was ignored (CVE-2024-0690)
|
|
diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml
|
|
index 3d3916a7fc..96d38e7f51 100644
|
|
--- a/lib/ansible/config/base.yml
|
|
+++ b/lib/ansible/config/base.yml
|
|
@@ -1757,7 +1757,7 @@ SHOW_CUSTOM_STATS:
|
|
type: bool
|
|
STRING_TYPE_FILTERS:
|
|
name: Filters to preserve strings
|
|
- default: [string, to_json, to_nice_json, to_yaml, ppretty, json]
|
|
+ default: [string, to_json, to_nice_json, to_yaml, to_nice_yaml, ppretty, json]
|
|
description:
|
|
- "This list of filters avoids 'type conversion' when templating variables"
|
|
- Useful when you want to avoid conversion into lists or dictionaries for JSON strings, for example.
|
|
diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py
|
|
index 0f4dc4e430..172963a218 100644
|
|
--- a/lib/ansible/playbook/base.py
|
|
+++ b/lib/ansible/playbook/base.py
|
|
@@ -613,7 +613,7 @@ class Base(FieldAttributeBase):
|
|
|
|
# flags and misc. settings
|
|
_environment = FieldAttribute(isa='list', extend=True, prepend=True)
|
|
- _no_log = FieldAttribute(isa='bool')
|
|
+ _no_log = FieldAttribute(isa='bool', default=C.DEFAULT_NO_LOG)
|
|
_run_once = FieldAttribute(isa='bool')
|
|
_ignore_errors = FieldAttribute(isa='bool')
|
|
_ignore_unreachable = FieldAttribute(isa='bool')
|
|
diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
|
|
index ac4fc0c568..be4b75986c 100644
|
|
--- a/lib/ansible/playbook/conditional.py
|
|
+++ b/lib/ansible/playbook/conditional.py
|
|
@@ -173,8 +173,8 @@ class Conditional:
|
|
)
|
|
try:
|
|
e = templar.environment.overlay()
|
|
- e.filters.update(templar._get_filters())
|
|
- e.tests.update(templar._get_tests())
|
|
+ e.filters.update(templar.environment.filters)
|
|
+ e.tests.update(templar.environment.tests)
|
|
|
|
res = e._parse(conditional, None, None)
|
|
res = generate(res, e, None, None)
|
|
diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py
|
|
index 10dd57aa3f..5b8b28526c 100644
|
|
--- a/lib/ansible/playbook/play_context.py
|
|
+++ b/lib/ansible/playbook/play_context.py
|
|
@@ -318,10 +318,6 @@ class PlayContext(Base):
|
|
if not new_info.connection_user:
|
|
new_info.connection_user = new_info.remote_user
|
|
|
|
- # set no_log to default if it was not previously set
|
|
- if new_info.no_log is None:
|
|
- new_info.no_log = C.DEFAULT_NO_LOG
|
|
-
|
|
if task.check_mode is not None:
|
|
new_info.check_mode = task.check_mode
|
|
|
|
diff --git a/lib/ansible/plugins/action/ce_template.py b/lib/ansible/plugins/action/ce_template.py
|
|
index 8d62b25647..4a72fbbfa8 100644
|
|
--- a/lib/ansible/plugins/action/ce_template.py
|
|
+++ b/lib/ansible/plugins/action/ce_template.py
|
|
@@ -100,5 +100,5 @@ class ActionModule(_ActionModule):
|
|
for role in dep_chain:
|
|
searchpath.append(role._role_path)
|
|
searchpath.append(os.path.dirname(source))
|
|
- self._templar.environment.loader.searchpath = searchpath
|
|
- self._task.args['src'] = self._templar.template(template_data)
|
|
+ with self._templar.set_temporary_context(searchpath=searchpath):
|
|
+ self._task.args['src'] = self._templar.template(template_data)
|
|
diff --git a/lib/ansible/plugins/action/network.py b/lib/ansible/plugins/action/network.py
|
|
index f0d0ca3ba7..d91c9b2af9 100644
|
|
--- a/lib/ansible/plugins/action/network.py
|
|
+++ b/lib/ansible/plugins/action/network.py
|
|
@@ -160,8 +160,8 @@ class ActionModule(_ActionModule):
|
|
for role in dep_chain:
|
|
searchpath.append(role._role_path)
|
|
searchpath.append(os.path.dirname(source))
|
|
- self._templar.environment.loader.searchpath = searchpath
|
|
- self._task.args['src'] = self._templar.template(template_data, convert_data=convert_data)
|
|
+ with self._templar.set_temporary_context(searchpath=searchpath):
|
|
+ self._task.args['src'] = self._templar.template(template_data, convert_data=convert_data)
|
|
|
|
def _get_network_os(self, task_vars):
|
|
if 'network_os' in self._task.args and self._task.args['network_os']:
|
|
diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py
|
|
index 8fb7393ff9..cede680ca6 100644
|
|
--- a/lib/ansible/plugins/action/template.py
|
|
+++ b/lib/ansible/plugins/action/template.py
|
|
@@ -17,7 +17,7 @@ from ansible.module_utils._text import to_bytes, to_text, to_native
|
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
from ansible.module_utils.six import string_types
|
|
from ansible.plugins.action import ActionBase
|
|
-from ansible.template import generate_ansible_template_vars
|
|
+from ansible.template import generate_ansible_template_vars, AnsibleEnvironment
|
|
|
|
|
|
class ActionModule(ActionBase):
|
|
@@ -127,27 +127,23 @@ class ActionModule(ActionBase):
|
|
newsearchpath.append(p)
|
|
searchpath = newsearchpath
|
|
|
|
- self._templar.environment.loader.searchpath = searchpath
|
|
- self._templar.environment.newline_sequence = newline_sequence
|
|
- if block_start_string is not None:
|
|
- self._templar.environment.block_start_string = block_start_string
|
|
- if block_end_string is not None:
|
|
- self._templar.environment.block_end_string = block_end_string
|
|
- if variable_start_string is not None:
|
|
- self._templar.environment.variable_start_string = variable_start_string
|
|
- if variable_end_string is not None:
|
|
- self._templar.environment.variable_end_string = variable_end_string
|
|
- self._templar.environment.trim_blocks = trim_blocks
|
|
- self._templar.environment.lstrip_blocks = lstrip_blocks
|
|
-
|
|
# add ansible 'template' vars
|
|
temp_vars = task_vars.copy()
|
|
temp_vars.update(generate_ansible_template_vars(source, dest))
|
|
|
|
- old_vars = self._templar.available_variables
|
|
- self._templar.available_variables = temp_vars
|
|
- resultant = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
|
|
- self._templar.available_variables = old_vars
|
|
+ # force templar to use AnsibleEnvironment to prevent issues with native types
|
|
+ # https://github.com/ansible/ansible/issues/46169
|
|
+ templar = self._templar.copy_with_new_env(environment_class=AnsibleEnvironment,
|
|
+ searchpath=searchpath,
|
|
+ newline_sequence=newline_sequence,
|
|
+ block_start_string=block_start_string,
|
|
+ block_end_string=block_end_string,
|
|
+ variable_start_string=variable_start_string,
|
|
+ variable_end_string=variable_end_string,
|
|
+ trim_blocks=trim_blocks,
|
|
+ lstrip_blocks=lstrip_blocks,
|
|
+ available_variables=temp_vars)
|
|
+ resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
|
|
except AnsibleAction:
|
|
raise
|
|
except Exception as e:
|
|
diff --git a/lib/ansible/plugins/lookup/template.py b/lib/ansible/plugins/lookup/template.py
|
|
index 4fd3584b65..c04b5e0d6a 100644
|
|
--- a/lib/ansible/plugins/lookup/template.py
|
|
+++ b/lib/ansible/plugins/lookup/template.py
|
|
@@ -17,7 +17,9 @@ DOCUMENTATION = """
|
|
description: list of files to template
|
|
convert_data:
|
|
type: bool
|
|
- description: whether to convert YAML into data. If False, strings that are YAML will be left untouched.
|
|
+ description:
|
|
+ - Whether to convert YAML into data. If False, strings that are YAML will be left untouched.
|
|
+ - Mutually exclusive with the jinja2_native option.
|
|
variable_start_string:
|
|
description: The string marking the beginning of a print statement.
|
|
default: '{{'
|
|
@@ -28,6 +30,16 @@ DOCUMENTATION = """
|
|
default: '}}'
|
|
version_added: '2.8'
|
|
type: str
|
|
+ jinja2_native:
|
|
+ description:
|
|
+ - Controls whether to use Jinja2 native types.
|
|
+ - It is off by default even if global jinja2_native is True.
|
|
+ - Has no effect if global jinja2_native is False.
|
|
+ - This offers more flexibility than the template module which does not use Jinja2 native types at all.
|
|
+ - Mutually exclusive with the convert_data option.
|
|
+ default: False
|
|
+ version_added: '2.11'
|
|
+ type: bool
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
@@ -51,24 +63,31 @@ import os
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
from ansible.module_utils._text import to_bytes, to_text
|
|
-from ansible.template import generate_ansible_template_vars
|
|
+from ansible.template import generate_ansible_template_vars, AnsibleEnvironment, USE_JINJA2_NATIVE
|
|
from ansible.utils.display import Display
|
|
|
|
+if USE_JINJA2_NATIVE:
|
|
+ from ansible.utils.native_jinja import NativeJinjaText
|
|
+
|
|
+
|
|
display = Display()
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
|
|
def run(self, terms, variables, **kwargs):
|
|
-
|
|
convert_data_p = kwargs.get('convert_data', True)
|
|
lookup_template_vars = kwargs.get('template_vars', {})
|
|
+ jinja2_native = kwargs.get('jinja2_native', False)
|
|
ret = []
|
|
|
|
variable_start_string = kwargs.get('variable_start_string', None)
|
|
variable_end_string = kwargs.get('variable_end_string', None)
|
|
|
|
- old_vars = self._templar.available_variables
|
|
+ if USE_JINJA2_NATIVE and not jinja2_native:
|
|
+ templar = self._templar.copy_with_new_env(environment_class=AnsibleEnvironment)
|
|
+ else:
|
|
+ templar = self._templar
|
|
|
|
for term in terms:
|
|
display.debug("File lookup term: %s" % term)
|
|
@@ -92,12 +111,6 @@ class LookupModule(LookupBase):
|
|
searchpath = newsearchpath
|
|
searchpath.insert(0, os.path.dirname(lookupfile))
|
|
|
|
- self._templar.environment.loader.searchpath = searchpath
|
|
- if variable_start_string is not None:
|
|
- self._templar.environment.variable_start_string = variable_start_string
|
|
- if variable_end_string is not None:
|
|
- self._templar.environment.variable_end_string = variable_end_string
|
|
-
|
|
# The template will have access to all existing variables,
|
|
# plus some added by ansible (e.g., template_{path,mtime}),
|
|
# plus anything passed to the lookup with the template_vars=
|
|
@@ -105,17 +118,20 @@ class LookupModule(LookupBase):
|
|
vars = deepcopy(variables)
|
|
vars.update(generate_ansible_template_vars(lookupfile))
|
|
vars.update(lookup_template_vars)
|
|
- self._templar.available_variables = vars
|
|
|
|
- # do the templating
|
|
- res = self._templar.template(template_data, preserve_trailing_newlines=True,
|
|
- convert_data=convert_data_p, escape_backslashes=False)
|
|
+ with templar.set_temporary_context(variable_start_string=variable_start_string,
|
|
+ variable_end_string=variable_end_string,
|
|
+ available_variables=vars, searchpath=searchpath):
|
|
+ res = templar.template(template_data, preserve_trailing_newlines=True,
|
|
+ convert_data=convert_data_p, escape_backslashes=False)
|
|
+
|
|
+ if USE_JINJA2_NATIVE and not jinja2_native:
|
|
+ # jinja2_native is true globally but off for the lookup, we need this text
|
|
+ # not to be processed by literal_eval anywhere in Ansible
|
|
+ res = NativeJinjaText(res)
|
|
|
|
ret.append(res)
|
|
else:
|
|
raise AnsibleError("the template file %s could not be found for the lookup" % term)
|
|
|
|
- # restore old variables
|
|
- self._templar.available_variables = old_vars
|
|
-
|
|
return ret
|
|
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
|
|
index 94ab31e58d..35c9dac194 100644
|
|
--- a/lib/ansible/template/__init__.py
|
|
+++ b/lib/ansible/template/__init__.py
|
|
@@ -28,6 +28,7 @@ import re
|
|
import time
|
|
|
|
from distutils.version import LooseVersion
|
|
+from contextlib import contextmanager
|
|
from numbers import Number
|
|
|
|
try:
|
|
@@ -42,8 +43,9 @@ from jinja2.runtime import Context, StrictUndefined
|
|
from ansible import constants as C
|
|
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable, AnsibleAssertionError
|
|
from ansible.module_utils.six import iteritems, string_types, text_type
|
|
+from ansible.module_utils.six.moves import range
|
|
from ansible.module_utils._text import to_native, to_text, to_bytes
|
|
-from ansible.module_utils.common._collections_compat import Sequence, Mapping, MutableMapping
|
|
+from ansible.module_utils.common._collections_compat import Iterator, Sequence, Mapping, MappingView, MutableMapping
|
|
from ansible.module_utils.common.collections import is_sequence
|
|
from ansible.module_utils.compat.importlib import import_module
|
|
from ansible.plugins.loader import filter_loader, lookup_loader, test_loader
|
|
@@ -71,12 +73,16 @@ NON_TEMPLATED_TYPES = (bool, Number)
|
|
JINJA2_OVERRIDE = '#jinja2:'
|
|
|
|
from jinja2 import __version__ as j2_version
|
|
+from jinja2 import Environment
|
|
+from jinja2.utils import concat as j2_concat
|
|
+
|
|
|
|
USE_JINJA2_NATIVE = False
|
|
if C.DEFAULT_JINJA2_NATIVE:
|
|
try:
|
|
- from jinja2.nativetypes import NativeEnvironment as Environment
|
|
- from ansible.template.native_helpers import ansible_native_concat as j2_concat
|
|
+ from jinja2.nativetypes import NativeEnvironment
|
|
+ from ansible.template.native_helpers import ansible_native_concat
|
|
+ from ansible.utils.native_jinja import NativeJinjaText
|
|
USE_JINJA2_NATIVE = True
|
|
except ImportError:
|
|
from jinja2 import Environment
|
|
@@ -85,15 +91,15 @@ if C.DEFAULT_JINJA2_NATIVE:
|
|
'jinja2_native requires Jinja 2.10 and above. '
|
|
'Version detected: %s. Falling back to default.' % j2_version
|
|
)
|
|
-else:
|
|
- from jinja2 import Environment
|
|
- from jinja2.utils import concat as j2_concat
|
|
|
|
|
|
JINJA2_BEGIN_TOKENS = frozenset(('variable_begin', 'block_begin', 'comment_begin', 'raw_begin'))
|
|
JINJA2_END_TOKENS = frozenset(('variable_end', 'block_end', 'comment_end', 'raw_end'))
|
|
|
|
|
|
+RANGE_TYPE = type(range(0))
|
|
+
|
|
+
|
|
def generate_ansible_template_vars(path, dest_path=None):
|
|
b_path = to_bytes(path)
|
|
try:
|
|
@@ -230,6 +236,60 @@ def recursive_check_defined(item):
|
|
raise AnsibleFilterError("{0} is undefined".format(item))
|
|
|
|
|
|
+def _is_rolled(value):
|
|
+ """Helper method to determine if something is an unrolled generator,
|
|
+ iterator, or similar object
|
|
+ """
|
|
+ return (
|
|
+ isinstance(value, Iterator) or
|
|
+ isinstance(value, MappingView) or
|
|
+ isinstance(value, RANGE_TYPE)
|
|
+ )
|
|
+
|
|
+
|
|
+def _unroll_iterator(func):
|
|
+ """Wrapper function, that intercepts the result of a filter
|
|
+ and auto unrolls a generator, so that users are not required to
|
|
+ explicitly use ``|list`` to unroll.
|
|
+ """
|
|
+ def wrapper(*args, **kwargs):
|
|
+ ret = func(*args, **kwargs)
|
|
+ if _is_rolled(ret):
|
|
+ return list(ret)
|
|
+ return ret
|
|
+
|
|
+ return _update_wrapper(wrapper, func)
|
|
+
|
|
+
|
|
+def _update_wrapper(wrapper, func):
|
|
+ # This code is duplicated from ``functools.update_wrapper`` from Py3.7.
|
|
+ # ``functools.update_wrapper`` was failing when the func was ``functools.partial``
|
|
+ for attr in ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'):
|
|
+ try:
|
|
+ value = getattr(func, attr)
|
|
+ except AttributeError:
|
|
+ pass
|
|
+ else:
|
|
+ setattr(wrapper, attr, value)
|
|
+ for attr in ('__dict__',):
|
|
+ getattr(wrapper, attr).update(getattr(func, attr, {}))
|
|
+ wrapper.__wrapped__ = func
|
|
+ return wrapper
|
|
+
|
|
+
|
|
+def _wrap_native_text(func):
|
|
+ """Wrapper function, that intercepts the result of a filter
|
|
+ and wraps it into NativeJinjaText which is then used
|
|
+ in ``ansible_native_concat`` to indicate that it is a text
|
|
+ which should not be passed into ``literal_eval``.
|
|
+ """
|
|
+ def wrapper(*args, **kwargs):
|
|
+ ret = func(*args, **kwargs)
|
|
+ return NativeJinjaText(ret)
|
|
+
|
|
+ return _update_wrapper(wrapper, func)
|
|
+
|
|
+
|
|
class AnsibleUndefined(StrictUndefined):
|
|
'''
|
|
A custom Undefined class, which returns further Undefined objects on access,
|
|
@@ -350,10 +410,11 @@ class AnsibleContext(Context):
|
|
|
|
|
|
class JinjaPluginIntercept(MutableMapping):
|
|
- def __init__(self, delegatee, pluginloader, *args, **kwargs):
|
|
+ def __init__(self, delegatee, pluginloader, jinja2_native, *args, **kwargs):
|
|
super(JinjaPluginIntercept, self).__init__(*args, **kwargs)
|
|
self._delegatee = delegatee
|
|
self._pluginloader = pluginloader
|
|
+ self._jinja2_native = jinja2_native
|
|
|
|
if self._pluginloader.class_name == 'FilterModule':
|
|
self._method_map_name = 'filters'
|
|
@@ -406,10 +467,13 @@ class JinjaPluginIntercept(MutableMapping):
|
|
|
|
method_map = getattr(plugin_impl, self._method_map_name)
|
|
|
|
- for f in iteritems(method_map()):
|
|
- fq_name = '.'.join((parent_prefix, f[0]))
|
|
+ for func_name, func in iteritems(method_map()):
|
|
+ fq_name = '.'.join((parent_prefix, func_name))
|
|
# FIXME: detect/warn on intra-collection function name collisions
|
|
- self._collection_jinja_func_cache[fq_name] = f[1]
|
|
+ if self._jinja2_native and func_name in C.STRING_TYPE_FILTERS:
|
|
+ self._collection_jinja_func_cache[fq_name] = _wrap_native_text(func)
|
|
+ else:
|
|
+ self._collection_jinja_func_cache[fq_name] = _unroll_iterator(func)
|
|
|
|
function_impl = self._collection_jinja_func_cache[key]
|
|
return function_impl
|
|
@@ -433,6 +497,9 @@ class AnsibleEnvironment(Environment):
|
|
'''
|
|
Our custom environment, which simply allows us to override the class-level
|
|
values for the Template and Context classes used by jinja2 internally.
|
|
+
|
|
+ NOTE: Any changes to this class must be reflected in
|
|
+ :class:`AnsibleNativeEnvironment` as well.
|
|
'''
|
|
context_class = AnsibleContext
|
|
template_class = AnsibleJ2Template
|
|
@@ -440,8 +507,27 @@ class AnsibleEnvironment(Environment):
|
|
def __init__(self, *args, **kwargs):
|
|
super(AnsibleEnvironment, self).__init__(*args, **kwargs)
|
|
|
|
- self.filters = JinjaPluginIntercept(self.filters, filter_loader)
|
|
- self.tests = JinjaPluginIntercept(self.tests, test_loader)
|
|
+ self.filters = JinjaPluginIntercept(self.filters, filter_loader, jinja2_native=False)
|
|
+ self.tests = JinjaPluginIntercept(self.tests, test_loader, jinja2_native=False)
|
|
+
|
|
+
|
|
+if USE_JINJA2_NATIVE:
|
|
+ class AnsibleNativeEnvironment(NativeEnvironment):
|
|
+ '''
|
|
+ Our custom environment, which simply allows us to override the class-level
|
|
+ values for the Template and Context classes used by jinja2 internally.
|
|
+
|
|
+ NOTE: Any changes to this class must be reflected in
|
|
+ :class:`AnsibleEnvironment` as well.
|
|
+ '''
|
|
+ context_class = AnsibleContext
|
|
+ template_class = AnsibleJ2Template
|
|
+
|
|
+ def __init__(self, *args, **kwargs):
|
|
+ super(AnsibleNativeEnvironment, self).__init__(*args, **kwargs)
|
|
+
|
|
+ self.filters = JinjaPluginIntercept(self.filters, filter_loader, jinja2_native=True)
|
|
+ self.tests = JinjaPluginIntercept(self.tests, test_loader, jinja2_native=True)
|
|
|
|
|
|
class Templar:
|
|
@@ -478,7 +564,9 @@ class Templar:
|
|
self._fail_on_filter_errors = True
|
|
self._fail_on_undefined_errors = C.DEFAULT_UNDEFINED_VAR_BEHAVIOR
|
|
|
|
- self.environment = AnsibleEnvironment(
|
|
+ environment_class = AnsibleNativeEnvironment if USE_JINJA2_NATIVE else AnsibleEnvironment
|
|
+
|
|
+ self.environment = environment_class(
|
|
trim_blocks=True,
|
|
undefined=AnsibleUndefined,
|
|
extensions=self._get_extensions(),
|
|
@@ -489,17 +577,50 @@ class Templar:
|
|
# the current rendering context under which the templar class is working
|
|
self.cur_context = None
|
|
|
|
+ # FIXME these regular expressions should be re-compiled each time variable_start_string and variable_end_string are changed
|
|
self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string))
|
|
-
|
|
- self._clean_regex = re.compile(r'(?:%s|%s|%s|%s)' % (
|
|
- self.environment.variable_start_string,
|
|
- self.environment.block_start_string,
|
|
- self.environment.block_end_string,
|
|
- self.environment.variable_end_string
|
|
- ))
|
|
self._no_type_regex = re.compile(r'.*?\|\s*(?:%s)(?:\([^\|]*\))?\s*\)?\s*(?:%s)' %
|
|
('|'.join(C.STRING_TYPE_FILTERS), self.environment.variable_end_string))
|
|
|
|
+ @property
|
|
+ def jinja2_native(self):
|
|
+ return not isinstance(self.environment, AnsibleEnvironment)
|
|
+
|
|
+ def copy_with_new_env(self, environment_class=AnsibleEnvironment, **kwargs):
|
|
+ r"""Creates a new copy of Templar with a new environment. The new environment is based on
|
|
+ given environment class and kwargs.
|
|
+
|
|
+ :kwarg environment_class: Environment class used for creating a new environment.
|
|
+ :kwarg \*\*kwargs: Optional arguments for the new environment that override existing
|
|
+ environment attributes.
|
|
+
|
|
+ :returns: Copy of Templar with updated environment.
|
|
+ """
|
|
+ # We need to use __new__ to skip __init__, mainly not to create a new
|
|
+ # environment there only to override it below
|
|
+ new_env = object.__new__(environment_class)
|
|
+ new_env.__dict__.update(self.environment.__dict__)
|
|
+
|
|
+ new_templar = object.__new__(Templar)
|
|
+ new_templar.__dict__.update(self.__dict__)
|
|
+ new_templar.environment = new_env
|
|
+
|
|
+ mapping = {
|
|
+ 'available_variables': new_templar,
|
|
+ 'searchpath': new_env.loader,
|
|
+ }
|
|
+
|
|
+ for key, value in kwargs.items():
|
|
+ obj = mapping.get(key, new_env)
|
|
+ try:
|
|
+ if value is not None:
|
|
+ setattr(obj, key, value)
|
|
+ except AttributeError:
|
|
+ # Ignore invalid attrs, lstrip_blocks was added in jinja2==2.7
|
|
+ pass
|
|
+
|
|
+ return new_templar
|
|
+
|
|
def _get_filters(self):
|
|
'''
|
|
Returns filter plugins, after loading and caching them if need be
|
|
@@ -513,6 +634,17 @@ class Templar:
|
|
for fp in self._filter_loader.all():
|
|
self._filters.update(fp.filters())
|
|
|
|
+ if self.jinja2_native:
|
|
+ for string_filter in C.STRING_TYPE_FILTERS:
|
|
+ try:
|
|
+ orig_filter = self._filters[string_filter]
|
|
+ except KeyError:
|
|
+ try:
|
|
+ orig_filter = self.environment.filters[string_filter]
|
|
+ except KeyError:
|
|
+ continue
|
|
+ self._filters[string_filter] = _wrap_native_text(orig_filter)
|
|
+
|
|
return self._filters.copy()
|
|
|
|
def _get_tests(self):
|
|
@@ -570,6 +702,36 @@ class Templar:
|
|
)
|
|
self.available_variables = variables
|
|
|
|
+ @contextmanager
|
|
+ def set_temporary_context(self, **kwargs):
|
|
+ """Context manager used to set temporary templating context, without having to worry about resetting
|
|
+ original values afterward
|
|
+
|
|
+ Use a keyword that maps to the attr you are setting. Applies to ``self.environment`` by default, to
|
|
+ set context on another object, it must be in ``mapping``.
|
|
+ """
|
|
+ mapping = {
|
|
+ 'available_variables': self,
|
|
+ 'searchpath': self.environment.loader,
|
|
+ }
|
|
+ original = {}
|
|
+
|
|
+ for key, value in kwargs.items():
|
|
+ obj = mapping.get(key, self.environment)
|
|
+ try:
|
|
+ original[key] = getattr(obj, key)
|
|
+ if value is not None:
|
|
+ setattr(obj, key, value)
|
|
+ except AttributeError:
|
|
+ # Ignore invalid attrs, lstrip_blocks was added in jinja2==2.7
|
|
+ pass
|
|
+
|
|
+ yield
|
|
+
|
|
+ for key in original:
|
|
+ obj = mapping.get(key, self.environment)
|
|
+ setattr(obj, key, original[key])
|
|
+
|
|
def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None,
|
|
convert_data=True, static_vars=None, cache=True, disable_lookups=False):
|
|
'''
|
|
@@ -632,7 +794,7 @@ class Templar:
|
|
disable_lookups=disable_lookups,
|
|
)
|
|
|
|
- if not USE_JINJA2_NATIVE:
|
|
+ if not self.jinja2_native:
|
|
unsafe = hasattr(result, '__UNSAFE__')
|
|
if convert_data and not self._no_type_regex.match(variable):
|
|
# if this looks like a dictionary or list, convert it to such using the safe_eval method
|
|
@@ -746,8 +908,18 @@ class Templar:
|
|
|
|
If using ANSIBLE_JINJA2_NATIVE we bypass this and return the actual value always
|
|
'''
|
|
- if USE_JINJA2_NATIVE:
|
|
+ if _is_rolled(thing):
|
|
+ # Auto unroll a generator, so that users are not required to
|
|
+ # explicitly use ``|list`` to unroll
|
|
+ # This only affects the scenario where the final result of templating
|
|
+ # is a generator, and not where a filter creates a generator in the middle
|
|
+ # of a template. See ``_unroll_iterator`` for the other case. This is probably
|
|
+ # unncessary
|
|
+ return list(thing)
|
|
+
|
|
+ if self.jinja2_native:
|
|
return thing
|
|
+
|
|
return thing if thing is not None else ''
|
|
|
|
def _fail_lookup(self, name, *args, **kwargs):
|
|
@@ -802,7 +974,10 @@ class Templar:
|
|
ran = wrap_var(ran)
|
|
else:
|
|
try:
|
|
- ran = wrap_var(",".join(ran))
|
|
+ if self.jinja2_native and isinstance(ran[0], NativeJinjaText):
|
|
+ ran = wrap_var(NativeJinjaText(",".join(ran)))
|
|
+ else:
|
|
+ ran = wrap_var(",".join(ran))
|
|
except TypeError:
|
|
# Lookup Plugins should always return lists. Throw an error if that's not
|
|
# the case:
|
|
@@ -824,7 +999,7 @@ class Templar:
|
|
raise AnsibleError("lookup plugin (%s) not found" % name)
|
|
|
|
def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, disable_lookups=False):
|
|
- if USE_JINJA2_NATIVE and not isinstance(data, string_types):
|
|
+ if self.jinja2_native and not isinstance(data, string_types):
|
|
return data
|
|
|
|
# For preserving the number of input newlines in the output (used
|
|
@@ -853,6 +1028,8 @@ class Templar:
|
|
|
|
# Adds Ansible custom filters and tests
|
|
myenv.filters.update(self._get_filters())
|
|
+ for k in myenv.filters:
|
|
+ myenv.filters[k] = _unroll_iterator(myenv.filters[k])
|
|
myenv.tests.update(self._get_tests())
|
|
|
|
if escape_backslashes:
|
|
@@ -904,7 +1081,7 @@ class Templar:
|
|
display.debug("failing because of a type error, template data is: %s" % to_text(data))
|
|
raise AnsibleError("Unexpected templating type error occurred on (%s): %s" % (to_native(data), to_native(te)))
|
|
|
|
- if USE_JINJA2_NATIVE and not isinstance(res, string_types):
|
|
+ if self.jinja2_native and not isinstance(res, string_types):
|
|
return res
|
|
|
|
if preserve_trailing_newlines:
|
|
diff --git a/lib/ansible/template/native_helpers.py b/lib/ansible/template/native_helpers.py
|
|
index 11c14b7fa1..84296ad9b6 100644
|
|
--- a/lib/ansible/template/native_helpers.py
|
|
+++ b/lib/ansible/template/native_helpers.py
|
|
@@ -14,6 +14,34 @@ from jinja2._compat import text_type
|
|
|
|
from jinja2.runtime import StrictUndefined
|
|
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
|
|
+from ansible.utils.native_jinja import NativeJinjaText
|
|
+
|
|
+
|
|
+def _fail_on_undefined(data):
|
|
+ """Recursively find an undefined value in a nested data structure
|
|
+ and properly raise the undefined exception.
|
|
+ """
|
|
+ if isinstance(data, Mapping):
|
|
+ for value in data.values():
|
|
+ _fail_on_undefined(value)
|
|
+ elif is_sequence(data):
|
|
+ for item in data:
|
|
+ _fail_on_undefined(item)
|
|
+ else:
|
|
+ if isinstance(data, StrictUndefined):
|
|
+ # To actually raise the undefined exception we need to
|
|
+ # access the undefined object otherwise the exception would
|
|
+ # be raised on the next access which might not be properly
|
|
+ # handled.
|
|
+ # See https://github.com/ansible/ansible/issues/52158
|
|
+ # and StrictUndefined implementation in upstream Jinja2.
|
|
+ str(data)
|
|
+
|
|
+ return data
|
|
+
|
|
+
|
|
+class NativeJinjaText(text_type):
|
|
+ pass
|
|
|
|
|
|
def ansible_native_concat(nodes):
|
|
@@ -49,9 +77,20 @@ def ansible_native_concat(nodes):
|
|
# We do that only here because it is taken care of by text_type() in the else block below already.
|
|
str(out)
|
|
|
|
+ if isinstance(out, NativeJinjaText):
|
|
+ # Sometimes (e.g. ``| string``) we need to mark variables
|
|
+ # in a special way so that they remain strings and are not
|
|
+ # passed into literal_eval.
|
|
+ # See:
|
|
+ # https://github.com/ansible/ansible/issues/70831
|
|
+ # https://github.com/pallets/jinja/issues/1200
|
|
+ # https://github.com/ansible/ansible/issues/70831#issuecomment-664190894
|
|
+ return out
|
|
+
|
|
# short circuit literal_eval when possible
|
|
if not isinstance(out, list):
|
|
return out
|
|
+
|
|
else:
|
|
if isinstance(nodes, types.GeneratorType):
|
|
nodes = chain(head, nodes)
|
|
diff --git a/lib/ansible/utils/native_jinja.py b/lib/ansible/utils/native_jinja.py
|
|
new file mode 100644
|
|
index 0000000000..53ef14004a
|
|
--- /dev/null
|
|
+++ b/lib/ansible/utils/native_jinja.py
|
|
@@ -0,0 +1,13 @@
|
|
+# Copyright: (c) 2020, Ansible Project
|
|
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
+
|
|
+# Make coding more python3-ish
|
|
+from __future__ import (absolute_import, division, print_function)
|
|
+__metaclass__ = type
|
|
+
|
|
+
|
|
+from ansible.module_utils.six import text_type
|
|
+
|
|
+
|
|
+class NativeJinjaText(text_type):
|
|
+ pass
|
|
diff --git a/lib/ansible/utils/unsafe_proxy.py b/lib/ansible/utils/unsafe_proxy.py
|
|
index 0bfd6340ff..54bebd177a 100644
|
|
--- a/lib/ansible/utils/unsafe_proxy.py
|
|
+++ b/lib/ansible/utils/unsafe_proxy.py
|
|
@@ -57,6 +57,7 @@ from ansible.module_utils._text import to_bytes, to_text
|
|
from ansible.module_utils.common._collections_compat import Mapping, Set
|
|
from ansible.module_utils.common.collections import is_sequence
|
|
from ansible.module_utils.six import string_types, binary_type, text_type
|
|
+from ansible.utils.native_jinja import NativeJinjaText
|
|
|
|
|
|
__all__ = ['AnsibleUnsafe', 'wrap_var']
|
|
@@ -331,6 +332,10 @@ class NativeJinjaUnsafeText(NativeJinjaText, AnsibleUnsafeText):
|
|
pass
|
|
|
|
|
|
+class NativeJinjaUnsafeText(NativeJinjaText, AnsibleUnsafeText):
|
|
+ pass
|
|
+
|
|
+
|
|
class UnsafeProxy(object):
|
|
def __new__(cls, obj, *args, **kwargs):
|
|
from ansible.utils.display import Display
|
|
@@ -376,6 +381,8 @@ def wrap_var(v):
|
|
v = _wrap_set(v)
|
|
elif is_sequence(v):
|
|
v = _wrap_sequence(v)
|
|
+ elif isinstance(v, NativeJinjaText):
|
|
+ v = NativeJinjaUnsafeText(v)
|
|
elif isinstance(v, binary_type):
|
|
v = AnsibleUnsafeBytes(v)
|
|
elif isinstance(v, text_type):
|
|
diff --git a/test/integration/targets/jinja2_native_types/test_casting.yml b/test/integration/targets/jinja2_native_types/test_casting.yml
|
|
index 5b4fe3ac0e..da06ab2e28 100644
|
|
--- a/test/integration/targets/jinja2_native_types/test_casting.yml
|
|
+++ b/test/integration/targets/jinja2_native_types/test_casting.yml
|
|
@@ -1,17 +1,22 @@
|
|
- name: cast things to other things
|
|
set_fact:
|
|
int_to_str: "{{ i_two|to_text }}"
|
|
+ int_to_str2: "{{ i_two | string }}"
|
|
str_to_int: "{{ s_two|int }}"
|
|
dict_to_str: "{{ dict_one|to_text }}"
|
|
list_to_str: "{{ list_one|to_text }}"
|
|
int_to_bool: "{{ i_one|bool }}"
|
|
str_true_to_bool: "{{ s_true|bool }}"
|
|
str_false_to_bool: "{{ s_false|bool }}"
|
|
+ list_to_json_str: "{{ list_one | to_json }}"
|
|
+ list_to_yaml_str: "{{ list_one | to_yaml }}"
|
|
|
|
- assert:
|
|
that:
|
|
- 'int_to_str == "2"'
|
|
- 'int_to_str|type_debug in ["str", "unicode"]'
|
|
+ - 'int_to_str2 == "2"'
|
|
+ - 'int_to_str2|type_debug in ["NativeJinjaText"]'
|
|
- 'str_to_int == 2'
|
|
- 'str_to_int|type_debug == "int"'
|
|
- 'dict_to_str|type_debug in ["str", "unicode"]'
|
|
@@ -22,3 +27,5 @@
|
|
- 'str_true_to_bool|type_debug == "bool"'
|
|
- 'str_false_to_bool is sameas false'
|
|
- 'str_false_to_bool|type_debug == "bool"'
|
|
+ - 'list_to_json_str|type_debug in ["NativeJinjaText"]'
|
|
+ - 'list_to_yaml_str|type_debug in ["NativeJinjaText"]'
|
|
diff --git a/test/integration/targets/jinja2_native_types/test_dunder.yml b/test/integration/targets/jinja2_native_types/test_dunder.yml
|
|
index 46fd4d0a90..df5ea9276b 100644
|
|
--- a/test/integration/targets/jinja2_native_types/test_dunder.yml
|
|
+++ b/test/integration/targets/jinja2_native_types/test_dunder.yml
|
|
@@ -20,4 +20,4 @@
|
|
|
|
- assert:
|
|
that:
|
|
- - 'const_dunder|type_debug in ["str", "unicode"]'
|
|
+ - 'const_dunder|type_debug in ["str", "unicode", "NativeJinjaText"]'
|
|
diff --git a/test/integration/targets/no_log/no_log_config.yml b/test/integration/targets/no_log/no_log_config.yml
|
|
new file mode 100644
|
|
index 0000000000..8a5088059d
|
|
--- /dev/null
|
|
+++ b/test/integration/targets/no_log/no_log_config.yml
|
|
@@ -0,0 +1,13 @@
|
|
+- hosts: testhost
|
|
+ gather_facts: false
|
|
+ tasks:
|
|
+ - debug:
|
|
+ no_log: true
|
|
+
|
|
+ - debug:
|
|
+ no_log: false
|
|
+
|
|
+ - debug:
|
|
+
|
|
+ - debug:
|
|
+ loop: '{{ range(3) }}'
|
|
diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh
|
|
index bb5c048fc9..8bfe019bb9 100755
|
|
--- a/test/integration/targets/no_log/runme.sh
|
|
+++ b/test/integration/targets/no_log/runme.sh
|
|
@@ -19,3 +19,8 @@ set -eux
|
|
|
|
# test invalid data passed to a suboption
|
|
[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]
|
|
+
|
|
+# test variations on ANSIBLE_NO_LOG
|
|
+[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
|
|
+[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
|
|
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ]
|
|
diff --git a/test/integration/targets/template_jinja2_non_native/46169.yml b/test/integration/targets/template_jinja2_non_native/46169.yml
|
|
new file mode 100644
|
|
index 0000000000..efb443eae0
|
|
--- /dev/null
|
|
+++ b/test/integration/targets/template_jinja2_non_native/46169.yml
|
|
@@ -0,0 +1,32 @@
|
|
+- hosts: localhost
|
|
+ gather_facts: no
|
|
+ tasks:
|
|
+ - set_fact:
|
|
+ output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}"
|
|
+
|
|
+ - template:
|
|
+ src: templates/46169.json.j2
|
|
+ dest: "{{ output_dir }}/result.json"
|
|
+
|
|
+ - command: "diff templates/46169.json.j2 {{ output_dir }}/result.json"
|
|
+ register: diff_result
|
|
+
|
|
+ - assert:
|
|
+ that:
|
|
+ - diff_result.stdout == ""
|
|
+
|
|
+ - block:
|
|
+ - set_fact:
|
|
+ non_native_lookup: "{{ lookup('template', 'templates/46169.json.j2') }}"
|
|
+
|
|
+ - assert:
|
|
+ that:
|
|
+ - non_native_lookup | type_debug == 'NativeJinjaUnsafeText'
|
|
+
|
|
+ - set_fact:
|
|
+ native_lookup: "{{ lookup('template', 'templates/46169.json.j2', jinja2_native=true) }}"
|
|
+
|
|
+ - assert:
|
|
+ that:
|
|
+ - native_lookup | type_debug == 'dict'
|
|
+ when: lookup('pipe', ansible_python_interpreter ~ ' -c "import jinja2; print(jinja2.__version__)"') is version('2.10', '>=')
|
|
diff --git a/test/integration/targets/template_jinja2_non_native/aliases b/test/integration/targets/template_jinja2_non_native/aliases
|
|
new file mode 100644
|
|
index 0000000000..b59832142f
|
|
--- /dev/null
|
|
+++ b/test/integration/targets/template_jinja2_non_native/aliases
|
|
@@ -0,0 +1 @@
|
|
+shippable/posix/group3
|
|
diff --git a/test/integration/targets/template_jinja2_non_native/runme.sh b/test/integration/targets/template_jinja2_non_native/runme.sh
|
|
new file mode 100755
|
|
index 0000000000..fe9d495a3e
|
|
--- /dev/null
|
|
+++ b/test/integration/targets/template_jinja2_non_native/runme.sh
|
|
@@ -0,0 +1,7 @@
|
|
+#!/usr/bin/env bash
|
|
+
|
|
+set -eux
|
|
+
|
|
+export ANSIBLE_JINJA2_NATIVE=1
|
|
+ansible-playbook 46169.yml -v "$@"
|
|
+unset ANSIBLE_JINJA2_NATIVE
|
|
diff --git a/test/integration/targets/template_jinja2_non_native/templates/46169.json.j2 b/test/integration/targets/template_jinja2_non_native/templates/46169.json.j2
|
|
new file mode 100644
|
|
index 0000000000..a4fc3f6717
|
|
--- /dev/null
|
|
+++ b/test/integration/targets/template_jinja2_non_native/templates/46169.json.j2
|
|
@@ -0,0 +1,3 @@
|
|
+{
|
|
+ "key": "bar"
|
|
+}
|
|
--
|
|
2.44.0
|
|
|