summaryrefslogtreecommitdiff
path: root/lib/ansible/template/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/template/__init__.py')
-rw-r--r--lib/ansible/template/__init__.py109
1 files changed, 59 insertions, 50 deletions
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index c45cfe35..05aab631 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -45,8 +45,8 @@ from ansible.errors import (
AnsibleOptionsError,
AnsibleUndefinedVariable,
)
-from ansible.module_utils.six import string_types, text_type
-from ansible.module_utils._text import to_native, to_text, to_bytes
+from ansible.module_utils.six import string_types
+from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible.module_utils.common.collections import is_sequence
from ansible.plugins.loader import filter_loader, lookup_loader, test_loader
from ansible.template.native_helpers import ansible_native_concat, ansible_eval_concat, ansible_concat
@@ -55,7 +55,7 @@ from ansible.template.vars import AnsibleJ2Vars
from ansible.utils.display import Display
from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.native_jinja import NativeJinjaText
-from ansible.utils.unsafe_proxy import wrap_var, AnsibleUnsafeText, AnsibleUnsafeBytes, NativeJinjaUnsafeText
+from ansible.utils.unsafe_proxy import to_unsafe_text, wrap_var, AnsibleUnsafeText, AnsibleUnsafeBytes, NativeJinjaUnsafeText
display = Display()
@@ -103,9 +103,9 @@ def generate_ansible_template_vars(path, fullpath=None, dest_path=None):
managed_str = managed_default.format(
host=temp_vars['template_host'],
uid=temp_vars['template_uid'],
- file=temp_vars['template_path'],
+ file=temp_vars['template_path'].replace('%', '%%'),
)
- temp_vars['ansible_managed'] = to_text(time.strftime(to_native(managed_str), time.localtime(os.path.getmtime(b_path))))
+ temp_vars['ansible_managed'] = to_unsafe_text(time.strftime(to_native(managed_str), time.localtime(os.path.getmtime(b_path))))
return temp_vars
@@ -130,7 +130,7 @@ def _escape_backslashes(data, jinja_env):
backslashes inside of a jinja2 expression.
"""
- if '\\' in data and '{{' in data:
+ if '\\' in data and jinja_env.variable_start_string in data:
new_data = []
d2 = jinja_env.preprocess(data)
in_var = False
@@ -153,6 +153,39 @@ def _escape_backslashes(data, jinja_env):
return data
+def _create_overlay(data, overrides, jinja_env):
+ if overrides is None:
+ overrides = {}
+
+ try:
+ has_override_header = data.startswith(JINJA2_OVERRIDE)
+ except (TypeError, AttributeError):
+ has_override_header = False
+
+ if overrides or has_override_header:
+ overlay = jinja_env.overlay(**overrides)
+ else:
+ overlay = jinja_env
+
+ # Get jinja env overrides from template
+ if has_override_header:
+ eol = data.find('\n')
+ line = data[len(JINJA2_OVERRIDE):eol]
+ data = data[eol + 1:]
+ for pair in line.split(','):
+ if ':' not in pair:
+ raise AnsibleError("failed to parse jinja2 override '%s'."
+ " Did you use something different from colon as key-value separator?" % pair.strip())
+ (key, val) = pair.split(':', 1)
+ key = key.strip()
+ if hasattr(overlay, key):
+ setattr(overlay, key, ast.literal_eval(val.strip()))
+ else:
+ display.warning(f"Could not find Jinja2 environment setting to override: '{key}'")
+
+ return data, overlay
+
+
def is_possibly_template(data, jinja_env):
"""Determines if a string looks like a template, by seeing if it
contains a jinja2 start delimiter. Does not guarantee that the string
@@ -532,7 +565,7 @@ class AnsibleEnvironment(NativeEnvironment):
'''
context_class = AnsibleContext
template_class = AnsibleJ2Template
- concat = staticmethod(ansible_eval_concat)
+ concat = staticmethod(ansible_eval_concat) # type: ignore[assignment]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -547,7 +580,7 @@ class AnsibleEnvironment(NativeEnvironment):
class AnsibleNativeEnvironment(AnsibleEnvironment):
- concat = staticmethod(ansible_native_concat)
+ concat = staticmethod(ansible_native_concat) # type: ignore[assignment]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -559,14 +592,7 @@ class Templar:
The main class for templating, with the main entry-point of template().
'''
- def __init__(self, loader, shared_loader_obj=None, variables=None):
- if shared_loader_obj is not None:
- display.deprecated(
- "The `shared_loader_obj` option to `Templar` is no longer functional, "
- "ansible.plugins.loader is used directly instead.",
- version='2.16',
- )
-
+ def __init__(self, loader, variables=None):
self._loader = loader
self._available_variables = {} if variables is None else variables
@@ -580,9 +606,6 @@ class Templar:
)
self.environment.template_class.environment_class = environment_class
- # jinja2 global is inconsistent across versions, this normalizes them
- self.environment.globals['dict'] = dict
-
# Custom globals
self.environment.globals['lookup'] = self._lookup
self.environment.globals['query'] = self.environment.globals['q'] = self._query_lookup
@@ -592,11 +615,14 @@ class Templar:
# the current rendering context under which the templar class is working
self.cur_context = None
- # FIXME this regex 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))
+ # this regex is re-compiled each time variable_start_string and variable_end_string are possibly changed
+ self._compile_single_var(self.environment)
self.jinja2_native = C.DEFAULT_JINJA2_NATIVE
+ def _compile_single_var(self, env):
+ self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (env.variable_start_string, env.variable_end_string))
+
def copy_with_new_env(self, environment_class=AnsibleEnvironment, **kwargs):
r"""Creates a new copy of Templar with a new environment.
@@ -719,7 +745,7 @@ class Templar:
variable = self._convert_bare_variable(variable)
if isinstance(variable, string_types):
- if not self.is_possibly_template(variable):
+ if not self.is_possibly_template(variable, overrides):
return variable
# Check to see if the string we are trying to render is just referencing a single
@@ -744,6 +770,7 @@ class Templar:
disable_lookups=disable_lookups,
convert_data=convert_data,
)
+ self._compile_single_var(self.environment)
return result
@@ -790,8 +817,9 @@ class Templar:
templatable = is_template
- def is_possibly_template(self, data):
- return is_possibly_template(data, self.environment)
+ def is_possibly_template(self, data, overrides=None):
+ data, env = _create_overlay(data, overrides, self.environment)
+ return is_possibly_template(data, env)
def _convert_bare_variable(self, variable):
'''
@@ -815,7 +843,7 @@ class Templar:
def _now_datetime(self, utc=False, fmt=None):
'''jinja2 global function to return current datetime, potentially formatted via strftime'''
if utc:
- now = datetime.datetime.utcnow()
+ now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
else:
now = datetime.datetime.now()
@@ -824,12 +852,12 @@ class Templar:
return now
- def _query_lookup(self, name, *args, **kwargs):
+ def _query_lookup(self, name, /, *args, **kwargs):
''' wrapper for lookup, force wantlist true'''
kwargs['wantlist'] = True
return self._lookup(name, *args, **kwargs)
- def _lookup(self, name, *args, **kwargs):
+ def _lookup(self, name, /, *args, **kwargs):
instance = lookup_loader.get(name, loader=self._loader, templar=self)
if instance is None:
@@ -932,31 +960,12 @@ class Templar:
if fail_on_undefined is None:
fail_on_undefined = self._fail_on_undefined_errors
- has_template_overrides = data.startswith(JINJA2_OVERRIDE)
-
try:
# NOTE Creating an overlay that lives only inside do_template means that overrides are not applied
# when templating nested variables in AnsibleJ2Vars where Templar.environment is used, not the overlay.
- # This is historic behavior that is kept for backwards compatibility.
- if overrides:
- myenv = self.environment.overlay(overrides)
- elif has_template_overrides:
- myenv = self.environment.overlay()
- else:
- myenv = self.environment
-
- # Get jinja env overrides from template
- if has_template_overrides:
- eol = data.find('\n')
- line = data[len(JINJA2_OVERRIDE):eol]
- data = data[eol + 1:]
- for pair in line.split(','):
- if ':' not in pair:
- raise AnsibleError("failed to parse jinja2 override '%s'."
- " Did you use something different from colon as key-value separator?" % pair.strip())
- (key, val) = pair.split(':', 1)
- key = key.strip()
- setattr(myenv, key, ast.literal_eval(val.strip()))
+ data, myenv = _create_overlay(data, overrides, self.environment)
+ # in case delimiters change
+ self._compile_single_var(myenv)
if escape_backslashes:
# Allow users to specify backslashes in playbooks as "\\" instead of as "\\\\".
@@ -964,7 +973,7 @@ class Templar:
try:
t = myenv.from_string(data)
- except TemplateSyntaxError as e:
+ except (TemplateSyntaxError, SyntaxError) as e:
raise AnsibleError("template error while templating string: %s. String: %s" % (to_native(e), to_native(data)), orig_exc=e)
except Exception as e:
if 'recursion' in to_native(e):