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
This commit is contained in:
Matt Martz 2019-10-25 09:51:57 -05:00 committed by GitHub
parent 92d6212026
commit cdb7ab61a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 35 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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']:

View file

@ -127,27 +127,16 @@ 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
with self._templar.set_temporary_context(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 = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
except AnsibleAction:
raise
except Exception as e:

View file

@ -68,8 +68,6 @@ class LookupModule(LookupBase):
variable_start_string = kwargs.get('variable_start_string', None)
variable_end_string = kwargs.get('variable_end_string', None)
old_vars = self._templar.available_variables
for term in terms:
display.debug("File lookup term: %s" % term)
@ -92,12 +90,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 +97,16 @@ 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 self._templar.set_temporary_context(variable_start_string=variable_start_string,
variable_end_string=variable_end_string,
available_variables=vars, searchpath=searchpath):
res = self._templar.template(template_data, preserve_trailing_newlines=True,
convert_data=convert_data_p, escape_backslashes=False)
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

View file

@ -27,6 +27,7 @@ import pwd
import re
import time
from contextlib import contextmanager
from numbers import Number
try:
@ -512,6 +513,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):
'''