diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index 7e0c559707a..e29a8bf1f09 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -30,7 +30,7 @@ from numbers import Number from jinja2 import Environment from jinja2.loaders import FileSystemLoader from jinja2.exceptions import TemplateSyntaxError, UndefinedError -from jinja2.utils import concat as j2_concat +from jinja2.utils import concat as j2_concat, missing from jinja2.runtime import Context, StrictUndefined from ansible import constants as C from ansible.compat.six import string_types, text_type @@ -154,15 +154,22 @@ class AnsibleContext(Context): return True return False + def _update_unsafe(self, val): + if val is not None and not self.unsafe and self._is_unsafe(val): + self.unsafe = True + def resolve(self, key): ''' The intercepted resolve(), which uses the helper above to set the internal flag whenever an unsafe variable value is returned. ''' val = super(AnsibleContext, self).resolve(key) - if val is not None and not self.unsafe: - if self._is_unsafe(val): - self.unsafe = True + self._update_unsafe(val) + return val + + def resolve_or_missing(self, key): + val = super(AnsibleContext, self).resolve_or_missing(key) + self._update_unsafe(val) return val class AnsibleEnvironment(Environment): diff --git a/lib/ansible/template/vars.py b/lib/ansible/template/vars.py index 0e9b99e884a..fc6140c297b 100644 --- a/lib/ansible/template/vars.py +++ b/lib/ansible/template/vars.py @@ -50,8 +50,11 @@ class AnsibleJ2Vars: self._locals = dict() if isinstance(locals, dict): for key, val in iteritems(locals): - if key[:2] == 'l_' and val is not missing: - self._locals[key[2:]] = val + if val is not missing: + if key[:2] == 'l_': + self._locals[key[2:]] = val + elif key not in ('context', 'environment', 'template'): + self._locals[key] = val def __contains__(self, k): if k in self._templar._available_variables: diff --git a/test/integration/targets/template/tasks/main.yml b/test/integration/targets/template/tasks/main.yml index c4f0f2910bb..d61b910c1e1 100644 --- a/test/integration/targets/template/tasks/main.yml +++ b/test/integration/targets/template/tasks/main.yml @@ -229,3 +229,17 @@ that: - "'templated_var_loaded' in lookup('file', '{{output_dir | expanduser }}/short.templated')" - "template_result|changed" + +# Create a template using a child template, to ensure that variables +# are passed properly from the parent to subtemplate context (issue #20063) + +- name: test parent and subtemplate creation of context + template: src=parent.j2 dest={{output_dir}}/parent_and_subtemplate.templated + register: template_result + +- stat: path={{output_dir}}/parent_and_subtemplate.templated + +- name: verify that the parent and subtemplate creation worked + assert: + that: + - "template_result|changed" diff --git a/test/integration/targets/template/templates/parent.j2 b/test/integration/targets/template/templates/parent.j2 new file mode 100644 index 00000000000..99a8e4cc631 --- /dev/null +++ b/test/integration/targets/template/templates/parent.j2 @@ -0,0 +1,3 @@ +{% for parent_item in parent_vars %} +{% include "subtemplate.j2" %} +{% endfor %} diff --git a/test/integration/targets/template/templates/subtemplate.j2 b/test/integration/targets/template/templates/subtemplate.j2 new file mode 100644 index 00000000000..f359bf2088c --- /dev/null +++ b/test/integration/targets/template/templates/subtemplate.j2 @@ -0,0 +1,2 @@ +{{ parent_item }} + diff --git a/test/integration/targets/template/vars/main.yml b/test/integration/targets/template/vars/main.yml index d811e17e3dd..9d45cf24535 100644 --- a/test/integration/targets/template/vars/main.yml +++ b/test/integration/targets/template/vars/main.yml @@ -13,3 +13,8 @@ templated_dict: null_type: "{{ null_type }}" bool: "{{ bool_var }}" multi_part: "{{ part_1 }}{{ part_2 }}" + +parent_vars: +- foo +- bar +- bam