Repository: ansible/ansible

Issue:
Bug: variables go out of scope inside an included template
##### Issue Type:

Bug Report
##### Ansible Version:

ansible 1.5
##### Environment:

debian 7
##### Summary:

Variables go out of scope inside an included template
##### Steps To Reproduce:

hosts.yml:

```

---
- hosts: all
  vars:
    mylist:
      - alpha
      - bravo
  tasks:
  - name: template test
    template: src=test.j2 dest=/tmp/testfile
```

test.j2:

```
{% for x in mylist %}

  X: {{ x }}

  {% include 'test-inc.j2' with context %}

{% endfor %}
```

test-inc.j2:

```
{{ x }}

```
##### Expected Results:

I expect x in test-inc.j2 to be properly dereferenced
##### Actual Results:

fatal: [myhost] => {'msg': "One or more undefined variables: 'x' is undefined", 'failed': True}
fatal: [myhost] => {'msg': "One or more undefined variables: 'x' is undefined", 'failed': True}



Discussion:
Also similar to #4391.

I also ran into this bug using Ansible 1.7.2. Seems it never got fixed as mentioned in #4391.
And yes, I think it is a bug and it's not the way Jinja2 works. [Jinja2 docs](http://jinja.pocoo.org/docs/dev/templates/#import-context-behavior) say that from version 2.1 the context passed to included templates should include variables defined in the template. And it indeed does work when rendering the template via Jinja2 API outside Ansible.
Is there any chance of fixing this? Because right now I'm thinking of writing my own template module using Jinja2 API directly as a workaround. The way you handle Jinja2 inside Ansible looks too complicated for me to tamper with.

I stand corrected, I was used to the old jinja issue. It seems we specifically disallow this in ansible's templating, I'm not sure why though. https://github.com/ansible/ansible/blob/devel/lib/ansible/utils/template.py#L201

Any workaround available?

# Closing This Ticket

Hi!

We believe the above commit should resolve this problem for you. This will also be included in the next major release.

If you continue seeing any problems related to this issue, or if you have any further questions, please let us know by stopping by one of the two mailing lists, as appropriate:
- https://groups.google.com/forum/#!forum/ansible-project - for user questions, tips, and tricks
- https://groups.google.com/forum/#!forum/ansible-devel - for strategy, future planning, and questions about writing code

Because this project is very active, we're unlikely to see comments made on closed tickets, but the mailing list is a great way to ask questions, or post if you don't think this particular issue is resolved.

Thank you!


Resolving patch:
diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py
--- a/lib/ansible/utils/template.py
+++ b/lib/ansible/utils/template.py
@@ -15,12 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 
+import sys
 import os
 import re
 import codecs
 import jinja2
 from jinja2.runtime import StrictUndefined
 from jinja2.exceptions import TemplateSyntaxError
+from jinja2.utils import missing
 import yaml
 import json
 from ansible import errors
@@ -157,16 +159,23 @@ class _jinja2_vars(object):
     extras is a list of locals to also search for variables.
     '''
 
-    def __init__(self, basedir, vars, globals, fail_on_undefined, *extras):
+    def __init__(self, basedir, vars, globals, fail_on_undefined, locals=None, *extras):
         self.basedir = basedir
         self.vars = vars
         self.globals = globals
         self.fail_on_undefined = fail_on_undefined
         self.extras = extras
+        self.locals = dict()
+        if isinstance(locals, dict):
+            for key, val in locals.iteritems():
+                if key[:2] == 'l_' and val is not missing:
+                    self.locals[key[2:]] = val
 
     def __contains__(self, k):
         if k in self.vars:
             return True
+        if k in self.locals:
+            return True
         for i in self.extras:
             if k in i:
                 return True
@@ -177,6 +186,8 @@ def __contains__(self, k):
     def __getitem__(self, varname):
         from ansible.runner import HostVars
         if varname not in self.vars:
+            if varname in self.locals:
+                return self.locals[varname]
             for i in self.extras:
                 if varname in i:
                     return i[varname]
@@ -200,7 +211,7 @@ def add_locals(self, locals):
         '''
         if locals is None:
             return self
-        return _jinja2_vars(self.basedir, self.vars, self.globals, self.fail_on_undefined, locals, *self.extras)
+        return _jinja2_vars(self.basedir, self.vars, self.globals, self.fail_on_undefined, locals=locals, *self.extras)
 
 class J2Template(jinja2.environment.Template):
     '''


Test patch: