Ability to add local variables into AnsibleJ2Vars was added in18a9eff11f
to fix #6653. Local variables are added using ``AnsibleJ2Vars.add_locals()`` method when creating a new context - typically when including/importing a template with context. For that use case local template variables created using ``set`` should override variables from higher contexts - either from the play or any parent template, or both; Jinja behaves the same way. Also removes AnsibleJ2Vars.extras instance variable which is not used. Also adds missing test for #6653. Fixes #72262 Fixes #72615 ci_complete (cherry picked from commita2af8432f3
)
This commit is contained in:
parent
e41d1f0a3f
commit
2c8c02c816
14 changed files with 93 additions and 22 deletions
2
changelogs/fragments/72615-jinja-import-context-fix.yml
Normal file
2
changelogs/fragments/72615-jinja-import-context-fix.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- Fix incorrect variable scoping when using ``import with context`` in Jinja2 templates. (https://github.com/ansible/ansible/issues/72615)
|
|
@ -40,7 +40,7 @@ class AnsibleJ2Vars(Mapping):
|
||||||
To facilitate using builtin jinja2 things like range, globals are also handled here.
|
To facilitate using builtin jinja2 things like range, globals are also handled here.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, templar, globals, locals=None, *extras):
|
def __init__(self, templar, globals, locals=None):
|
||||||
'''
|
'''
|
||||||
Initializes this object with a valid Templar() object, as
|
Initializes this object with a valid Templar() object, as
|
||||||
well as several dictionaries of variables representing
|
well as several dictionaries of variables representing
|
||||||
|
@ -49,7 +49,6 @@ class AnsibleJ2Vars(Mapping):
|
||||||
|
|
||||||
self._templar = templar
|
self._templar = templar
|
||||||
self._globals = globals
|
self._globals = globals
|
||||||
self._extras = extras
|
|
||||||
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):
|
||||||
|
@ -60,40 +59,33 @@ class AnsibleJ2Vars(Mapping):
|
||||||
self._locals[key] = val
|
self._locals[key] = val
|
||||||
|
|
||||||
def __contains__(self, k):
|
def __contains__(self, k):
|
||||||
if k in self._templar.available_variables:
|
|
||||||
return True
|
|
||||||
if k in self._locals:
|
if k in self._locals:
|
||||||
return True
|
return True
|
||||||
for i in self._extras:
|
if k in self._templar.available_variables:
|
||||||
if k in i:
|
return True
|
||||||
return True
|
|
||||||
if k in self._globals:
|
if k in self._globals:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
keys = set()
|
keys = set()
|
||||||
keys.update(self._templar.available_variables, self._locals, self._globals, *self._extras)
|
keys.update(self._templar.available_variables, self._locals, self._globals)
|
||||||
return iter(keys)
|
return iter(keys)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
keys = set()
|
keys = set()
|
||||||
keys.update(self._templar.available_variables, self._locals, self._globals, *self._extras)
|
keys.update(self._templar.available_variables, self._locals, self._globals)
|
||||||
return len(keys)
|
return len(keys)
|
||||||
|
|
||||||
def __getitem__(self, varname):
|
def __getitem__(self, varname):
|
||||||
if varname not in self._templar.available_variables:
|
if varname in self._locals:
|
||||||
if varname in self._locals:
|
return self._locals[varname]
|
||||||
return self._locals[varname]
|
if varname in self._templar.available_variables:
|
||||||
for i in self._extras:
|
variable = self._templar.available_variables[varname]
|
||||||
if varname in i:
|
elif varname in self._globals:
|
||||||
return i[varname]
|
return self._globals[varname]
|
||||||
if varname in self._globals:
|
else:
|
||||||
return self._globals[varname]
|
raise KeyError("undefined variable: %s" % varname)
|
||||||
else:
|
|
||||||
raise KeyError("undefined variable: %s" % varname)
|
|
||||||
|
|
||||||
variable = self._templar.available_variables[varname]
|
|
||||||
|
|
||||||
# HostVars is special, return it as-is, as is the special variable
|
# HostVars is special, return it as-is, as is the special variable
|
||||||
# 'vars', which contains the vars structure
|
# 'vars', which contains the vars structure
|
||||||
|
@ -127,4 +119,4 @@ class AnsibleJ2Vars(Mapping):
|
||||||
new_locals = self._locals.copy()
|
new_locals = self._locals.copy()
|
||||||
new_locals.update(locals)
|
new_locals.update(locals)
|
||||||
|
|
||||||
return AnsibleJ2Vars(self._templar, self._globals, locals=new_locals, *self._extras)
|
return AnsibleJ2Vars(self._templar, self._globals, locals=new_locals)
|
||||||
|
|
10
test/integration/targets/template/6653.yml
Normal file
10
test/integration/targets/template/6653.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
- hosts: localhost
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
mylist:
|
||||||
|
- alpha
|
||||||
|
- bravo
|
||||||
|
tasks:
|
||||||
|
- name: Should not fail on undefined variable
|
||||||
|
set_fact:
|
||||||
|
template_result: "{{ lookup('template', '6653.j2') }}"
|
6
test/integration/targets/template/72262.yml
Normal file
6
test/integration/targets/template/72262.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
- hosts: localhost
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Should not fail on undefined variable
|
||||||
|
set_fact:
|
||||||
|
template_result: "{{ lookup('template', '72262.j2') }}"
|
26
test/integration/targets/template/72615.yml
Normal file
26
test/integration/targets/template/72615.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
- hosts: localhost
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
foo: "top-level-foo"
|
||||||
|
tasks:
|
||||||
|
- set_fact:
|
||||||
|
template_result: "{{ lookup('template', '72615.j2') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'template-level-bar' in template_result"
|
||||||
|
- "'template-nested-level-bar' in template_result"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'top-level-foo' not in template_result"
|
||||||
|
- "'template-level-foo' in template_result"
|
||||||
|
- "'template-nested-level-foo' in template_result"
|
||||||
|
when: lookup('pipe', ansible_python_interpreter ~ ' -c "import jinja2; print(jinja2.__version__)"') is version('2.9', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'top-level-foo' in template_result"
|
||||||
|
- "'template-level-foo' not in template_result"
|
||||||
|
- "'template-nested-level-foo' not in template_result"
|
||||||
|
when: lookup('pipe', ansible_python_interpreter ~ ' -c "import jinja2; print(jinja2.__version__)"') is version('2.9', '<')
|
|
@ -25,3 +25,12 @@ ansible-playbook unused_vars_include.yml -v "$@"
|
||||||
|
|
||||||
# https://github.com/ansible/ansible/issues/55152
|
# https://github.com/ansible/ansible/issues/55152
|
||||||
ansible-playbook undefined_var_info.yml -v "$@"
|
ansible-playbook undefined_var_info.yml -v "$@"
|
||||||
|
|
||||||
|
# https://github.com/ansible/ansible/issues/72615
|
||||||
|
ansible-playbook 72615.yml -v "$@"
|
||||||
|
|
||||||
|
# https://github.com/ansible/ansible/issues/6653
|
||||||
|
ansible-playbook 6653.yml -v "$@"
|
||||||
|
|
||||||
|
# https://github.com/ansible/ansible/issues/72262
|
||||||
|
ansible-playbook 72262.yml -v "$@"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{{ x }}
|
4
test/integration/targets/template/templates/6653.j2
Normal file
4
test/integration/targets/template/templates/6653.j2
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% for x in mylist %}
|
||||||
|
{{ x }}
|
||||||
|
{% include '6653-include.j2' with context %}
|
||||||
|
{% endfor %}
|
|
@ -0,0 +1 @@
|
||||||
|
{{ vars.test }}
|
|
@ -0,0 +1 @@
|
||||||
|
{% set test = "I'm test variable" %}
|
3
test/integration/targets/template/templates/72262.j2
Normal file
3
test/integration/targets/template/templates/72262.j2
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% import '72262-vars.j2' as vars with context %}
|
||||||
|
{% macro included() %}{% include '72262-included.j2' %}{% endmacro %}
|
||||||
|
{{ included()|indent }}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% macro print_context_vars_nested(value) %}
|
||||||
|
foo: {{ foo }}
|
||||||
|
bar: {{ value }}
|
||||||
|
{% endmacro %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% macro print_context_vars(value) %}
|
||||||
|
{{ foo }}
|
||||||
|
{{ value }}
|
||||||
|
{% set foo = "template-nested-level-foo" %}
|
||||||
|
{% set bar = "template-nested-level-bar" %}
|
||||||
|
{% from '72615-macro-nested.j2' import print_context_vars_nested with context %}
|
||||||
|
{{ print_context_vars_nested(bar) }}
|
||||||
|
{% endmacro %}
|
4
test/integration/targets/template/templates/72615.j2
Normal file
4
test/integration/targets/template/templates/72615.j2
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% set foo = "template-level-foo" %}
|
||||||
|
{% set bar = "template-level-bar" %}
|
||||||
|
{% from '72615-macro.j2' import print_context_vars with context %}
|
||||||
|
{{ print_context_vars(bar) }}
|
Loading…
Reference in a new issue