Don't restrict local jinja2 variables to those that start with l_

Per a change in jinja2 2.9, local variables no longer are prefixed
with l_, so this updates AnsibleJ2Vars to pull in all locals (while
excluding some) regardless of name.

Fixes #20063

(cherry picked from commit 4d49b317929b86e1fc1b0cbace825ff73b372dc7)
This commit is contained in:
James Cammarata 2017-01-19 23:13:09 -06:00
commit 188c3c608a
6 changed files with 40 additions and 6 deletions

View file

@ -30,7 +30,7 @@ from numbers import Number
from jinja2 import Environment from jinja2 import Environment
from jinja2.loaders import FileSystemLoader from jinja2.loaders import FileSystemLoader
from jinja2.exceptions import TemplateSyntaxError, UndefinedError 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 jinja2.runtime import Context, StrictUndefined
from ansible import constants as C from ansible import constants as C
from ansible.compat.six import string_types, text_type from ansible.compat.six import string_types, text_type
@ -154,15 +154,22 @@ class AnsibleContext(Context):
return True return True
return False 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): def resolve(self, key):
''' '''
The intercepted resolve(), which uses the helper above to set the The intercepted resolve(), which uses the helper above to set the
internal flag whenever an unsafe variable value is returned. internal flag whenever an unsafe variable value is returned.
''' '''
val = super(AnsibleContext, self).resolve(key) val = super(AnsibleContext, self).resolve(key)
if val is not None and not self.unsafe: self._update_unsafe(val)
if self._is_unsafe(val): return val
self.unsafe = True
def resolve_or_missing(self, key):
val = super(AnsibleContext, self).resolve_or_missing(key)
self._update_unsafe(val)
return val return val
class AnsibleEnvironment(Environment): class AnsibleEnvironment(Environment):

View file

@ -50,8 +50,11 @@ class AnsibleJ2Vars:
self._locals = dict() self._locals = dict()
if isinstance(locals, dict): if isinstance(locals, dict):
for key, val in iteritems(locals): for key, val in iteritems(locals):
if key[:2] == 'l_' and val is not missing: if val is not missing:
if key[:2] == 'l_':
self._locals[key[2:]] = val self._locals[key[2:]] = val
elif key not in ('context', 'environment', 'template'):
self._locals[key] = val
def __contains__(self, k): def __contains__(self, k):
if k in self._templar._available_variables: if k in self._templar._available_variables:

View file

@ -229,3 +229,17 @@
that: that:
- "'templated_var_loaded' in lookup('file', '{{output_dir | expanduser }}/short.templated')" - "'templated_var_loaded' in lookup('file', '{{output_dir | expanduser }}/short.templated')"
- "template_result|changed" - "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"

View file

@ -0,0 +1,3 @@
{% for parent_item in parent_vars %}
{% include "subtemplate.j2" %}
{% endfor %}

View file

@ -0,0 +1,2 @@
{{ parent_item }}

View file

@ -13,3 +13,8 @@ templated_dict:
null_type: "{{ null_type }}" null_type: "{{ null_type }}"
bool: "{{ bool_var }}" bool: "{{ bool_var }}"
multi_part: "{{ part_1 }}{{ part_2 }}" multi_part: "{{ part_1 }}{{ part_2 }}"
parent_vars:
- foo
- bar
- bam