summaryrefslogtreecommitdiff
path: root/lib/ansible/template
diff options
context:
space:
mode:
authorjctanner <tanner.jc@gmail.com>2018-05-31 04:38:29 -0400
committerMartin Krizek <martin.krizek@gmail.com>2018-05-31 10:38:29 +0200
commita9e53cdb68cfb80a69297a8ab6266d0654d9b6a1 (patch)
tree3f6d5835945f3d065be0557991a994ccbdfa4dc9 /lib/ansible/template
parent81510970ae7f8a423309cca0a315a2deebcb64d3 (diff)
downloadansible-a9e53cdb68cfb80a69297a8ab6266d0654d9b6a1.tar.gz
Allow config to enable native jinja types (#32738)
Co-authored-by: Martin Krizek <martin.krizek@gmail.com>
Diffstat (limited to 'lib/ansible/template')
-rw-r--r--lib/ansible/template/__init__.py57
-rw-r--r--lib/ansible/template/native_helpers.py44
2 files changed, 83 insertions, 18 deletions
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index 401d5e7bb4..9f03a5a40d 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -37,11 +37,9 @@ try:
except ImportError:
from sha import sha as sha1
-from jinja2 import Environment
from jinja2.exceptions import TemplateSyntaxError, UndefinedError
from jinja2.loaders import FileSystemLoader
from jinja2.runtime import Context, StrictUndefined
-from jinja2.utils import concat as j2_concat
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable, AnsibleAssertionError
@@ -70,6 +68,19 @@ NON_TEMPLATED_TYPES = (bool, Number)
JINJA2_OVERRIDE = '#jinja2:'
+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
+ USE_JINJA2_NATIVE = True
+ except ImportError:
+ from jinja2 import Environment
+ from jinja2.utils import concat as j2_concat
+else:
+ from jinja2 import Environment
+ from jinja2.utils import concat as j2_concat
+
def generate_ansible_template_vars(path):
b_path = to_bytes(path)
@@ -479,19 +490,20 @@ class Templar:
disable_lookups=disable_lookups,
)
- 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
- if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
- result.startswith("[") or result in ("True", "False"):
- eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
- if eval_results[1] is None:
- result = eval_results[0]
- if unsafe:
- result = wrap_var(result)
- else:
- # FIXME: if the safe_eval raised an error, should we do something with it?
- pass
+ if not USE_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
+ if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
+ result.startswith("[") or result in ("True", "False"):
+ eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
+ if eval_results[1] is None:
+ result = eval_results[0]
+ if unsafe:
+ result = wrap_var(result)
+ else:
+ # FIXME: if the safe_eval raised an error, should we do something with it?
+ pass
# we only cache in the case where we have a single variable
# name, to make sure we're not putting things which may otherwise
@@ -663,9 +675,15 @@ 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):
+ return data
+
# For preserving the number of input newlines in the output (used
# later in this method)
- data_newlines = _count_newlines_from_end(data)
+ if not USE_JINJA2_NATIVE:
+ data_newlines = _count_newlines_from_end(data)
+ else:
+ data_newlines = None
if fail_on_undefined is None:
fail_on_undefined = self._fail_on_undefined_errors
@@ -678,7 +696,7 @@ class Templar:
myenv = self.environment.overlay(overrides)
# Get jinja env overrides from template
- if data.startswith(JINJA2_OVERRIDE):
+ if hasattr(data, 'startswith') and data.startswith(JINJA2_OVERRIDE):
eol = data.find('\n')
line = data[len(JINJA2_OVERRIDE):eol]
data = data[eol + 1:]
@@ -720,7 +738,7 @@ class Templar:
try:
res = j2_concat(rf)
- if new_context.unsafe:
+ if getattr(new_context, 'unsafe', False):
res = wrap_var(res)
except TypeError as te:
if 'StrictUndefined' in to_native(te):
@@ -731,6 +749,9 @@ class Templar:
display.debug("failing because of a type error, template data is: %s" % to_native(data))
raise AnsibleError("Unexpected templating type error occurred on (%s): %s" % (to_native(data), to_native(te)))
+ if USE_JINJA2_NATIVE:
+ return res
+
if preserve_trailing_newlines:
# The low level calls above do not preserve the newline
# characters at the end of the input data, so we use the
diff --git a/lib/ansible/template/native_helpers.py b/lib/ansible/template/native_helpers.py
new file mode 100644
index 0000000000..d68f849ee7
--- /dev/null
+++ b/lib/ansible/template/native_helpers.py
@@ -0,0 +1,44 @@
+# Copyright: (c) 2018, 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 ast import literal_eval
+from itertools import islice, chain
+import types
+
+from jinja2._compat import text_type
+
+
+def ansible_native_concat(nodes):
+ """Return a native Python type from the list of compiled nodes. If the
+ result is a single node, its value is returned. Otherwise, the nodes are
+ concatenated as strings. If the result can be parsed with
+ :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
+ string is returned.
+ """
+
+ # https://github.com/pallets/jinja/blob/master/jinja2/nativetypes.py
+
+ head = list(islice(nodes, 2))
+
+ if not head:
+ return None
+
+ if len(head) == 1:
+ out = head[0]
+ # short circuit literal_eval when possible
+ if not isinstance(out, list): # FIXME is this needed?
+ return out
+ else:
+ if isinstance(nodes, types.GeneratorType):
+ nodes = chain(head, nodes)
+ out = u''.join([text_type(v) for v in nodes])
+
+ try:
+ return literal_eval(out)
+ except (ValueError, SyntaxError, MemoryError):
+ return out