several fixes to template
- now obeys global undefined var setting and allows override (mostly for with_ ) - moved environment instanciation to init instead of each template call - removed hardcoded template token matching and now use actually configured tokens, now it won't break if someone changes default configs in ansible.cfg - made reenetrant template calls now pass the same data it got, dictionary and lists were loosing existing and new params - moved fail_on_undeinfed parameter to template call, as it should only realky be set to false on specific templates and not globally - added overrides, which will allow template to implement jinja2 header override features - added filter list to overrides to disallow possibly insecure ones, TODO: check if this is still needed as facts should not be templated anymore - TODO: actually implement jinja2 header overrides
This commit is contained in:
parent
7291f9e965
commit
4098e8283e
1 changed files with 32 additions and 19 deletions
|
@ -40,20 +40,19 @@ __all__ = ['Templar']
|
|||
|
||||
# A regex for checking to see if a variable we're trying to
|
||||
# expand is just a single variable name.
|
||||
SINGLE_VAR = re.compile(r"^{{\s*(\w*)\s*}}$")
|
||||
|
||||
# Primitive Types which we don't want Jinja to convert to strings.
|
||||
NON_TEMPLATED_TYPES = ( bool, Number )
|
||||
|
||||
JINJA2_OVERRIDE = '#jinja2:'
|
||||
JINJA2_ALLOWED_OVERRIDES = ['trim_blocks', 'lstrip_blocks', 'newline_sequence', 'keep_trailing_newline']
|
||||
JINJA2_ALLOWED_OVERRIDES = frozenset(['trim_blocks', 'lstrip_blocks', 'newline_sequence', 'keep_trailing_newline'])
|
||||
|
||||
class Templar:
|
||||
'''
|
||||
The main class for templating, with the main entry-point of template().
|
||||
'''
|
||||
|
||||
def __init__(self, loader, shared_loader_obj=None, variables=dict(), fail_on_undefined=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR):
|
||||
def __init__(self, loader, shared_loader_obj=None, variables=dict()):
|
||||
self._loader = loader
|
||||
self._basedir = loader.get_basedir()
|
||||
self._filters = None
|
||||
|
@ -70,7 +69,12 @@ class Templar:
|
|||
# should result in fatal errors being raised
|
||||
self._fail_on_lookup_errors = True
|
||||
self._fail_on_filter_errors = True
|
||||
self._fail_on_undefined_errors = fail_on_undefined
|
||||
self._fail_on_undefined_errors = C.DEFAULT_UNDEFINED_VAR_BEHAVIOR
|
||||
|
||||
self.environment = Environment(trim_blocks=True, undefined=StrictUndefined, extensions=self._get_extensions(), finalize=self._finalize)
|
||||
self.environment.template_class = AnsibleJ2Template
|
||||
|
||||
self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string))
|
||||
|
||||
def _count_newlines_from_end(self, in_str):
|
||||
'''
|
||||
|
@ -129,7 +133,7 @@ class Templar:
|
|||
assert isinstance(variables, dict)
|
||||
self._available_variables = variables.copy()
|
||||
|
||||
def template(self, variable, convert_bare=False, preserve_trailing_newlines=False):
|
||||
def template(self, variable, convert_bare=False, preserve_trailing_newlines=False, fail_on_undefined=None, overrides=None):
|
||||
'''
|
||||
Templates (possibly recursively) any given data as input. If convert_bare is
|
||||
set to True, the given data will be wrapped as a jinja2 variable ('{{foo}}')
|
||||
|
@ -147,7 +151,7 @@ class Templar:
|
|||
# Check to see if the string we are trying to render is just referencing a single
|
||||
# var. In this case we don't want to accidentally change the type of the variable
|
||||
# to a string by using the jinja template renderer. We just want to pass it.
|
||||
only_one = SINGLE_VAR.match(variable)
|
||||
only_one = self.SINGLE_VAR.match(variable)
|
||||
if only_one:
|
||||
var_name = only_one.group(1)
|
||||
if var_name in self._available_variables:
|
||||
|
@ -155,10 +159,10 @@ class Templar:
|
|||
if isinstance(resolved_val, NON_TEMPLATED_TYPES):
|
||||
return resolved_val
|
||||
|
||||
result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines)
|
||||
result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides)
|
||||
|
||||
# if this looks like a dictionary or list, convert it to such using the safe_eval method
|
||||
if (result.startswith("{") and not result.startswith("{{")) or result.startswith("["):
|
||||
if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or result.startswith("["):
|
||||
eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
|
||||
if eval_results[1] is None:
|
||||
result = eval_results[0]
|
||||
|
@ -169,11 +173,11 @@ class Templar:
|
|||
return result
|
||||
|
||||
elif isinstance(variable, (list, tuple)):
|
||||
return [self.template(v, convert_bare=convert_bare) for v in variable]
|
||||
return [self.template(v, convert_bare=convert_bare, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides) for v in variable]
|
||||
elif isinstance(variable, dict):
|
||||
d = {}
|
||||
for (k, v) in variable.iteritems():
|
||||
d[k] = self.template(v, convert_bare=convert_bare)
|
||||
d[k] = self.template(v, convert_bare=convert_bare, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides)
|
||||
return d
|
||||
else:
|
||||
return variable
|
||||
|
@ -188,7 +192,7 @@ class Templar:
|
|||
'''
|
||||
returns True if the data contains a variable pattern
|
||||
'''
|
||||
return "$" in data or "{{" in data or '{%' in data
|
||||
return self.environment.block_start_string in data or self.environment.variable_start_string in data
|
||||
|
||||
def _convert_bare_variable(self, variable):
|
||||
'''
|
||||
|
@ -198,8 +202,8 @@ class Templar:
|
|||
|
||||
if isinstance(variable, basestring):
|
||||
first_part = variable.split(".")[0].split("[")[0]
|
||||
if first_part in self._available_variables and '{{' not in variable and '$' not in variable:
|
||||
return "{{%s}}" % variable
|
||||
if first_part in self._available_variables and self.environment.variable_start_string not in variable:
|
||||
return "%s%s%s" % (self.environment.variable_start_string, variable, self.environment.variable_end_string)
|
||||
|
||||
# the variable didn't meet the conditions to be converted,
|
||||
# so just return it as-is
|
||||
|
@ -230,16 +234,24 @@ class Templar:
|
|||
else:
|
||||
raise AnsibleError("lookup plugin (%s) not found" % name)
|
||||
|
||||
def _do_template(self, data, preserve_trailing_newlines=False):
|
||||
def _do_template(self, data, preserve_trailing_newlines=False, fail_on_undefined=None, overrides=None):
|
||||
|
||||
if fail_on_undefined is None:
|
||||
fail_on_undefined = self._fail_on_undefined_errors
|
||||
|
||||
try:
|
||||
# allows template header overrides to change jinja2 options.
|
||||
if overrides is None:
|
||||
myenv = self.environment.overlay()
|
||||
else:
|
||||
overrides = JINJA2_ALLOWED_OVERRIDES.intersection(set(overrides))
|
||||
myenv = self.environment.overlay(overrides)
|
||||
|
||||
environment = Environment(trim_blocks=True, undefined=StrictUndefined, extensions=self._get_extensions(), finalize=self._finalize)
|
||||
environment.filters.update(self._get_filters())
|
||||
environment.template_class = AnsibleJ2Template
|
||||
#FIXME: add tests
|
||||
myenv.filters.update(self._get_filters())
|
||||
|
||||
try:
|
||||
t = environment.from_string(data)
|
||||
t = myenv.from_string(data)
|
||||
except TemplateSyntaxError, e:
|
||||
raise AnsibleError("template error while templating string: %s" % str(e))
|
||||
except Exception, e:
|
||||
|
@ -280,8 +292,9 @@ class Templar:
|
|||
|
||||
return res
|
||||
except (UndefinedError, AnsibleUndefinedVariable), e:
|
||||
if self._fail_on_undefined_errors:
|
||||
if fail_on_undefined:
|
||||
raise
|
||||
else:
|
||||
#TODO: return warning about undefined var
|
||||
return data
|
||||
|
||||
|
|
Loading…
Reference in a new issue