Repository: ansible/ansible

Issue:
Included playbooks not able to access variables from vars_files
##### Issue Type:

Bug Report
##### Ansible Version:

1.6.1 (existed in 1.5 also)
##### Environment:

NA
##### Summary:

Variables defined in vars is available to included playbooks but variables included via vars_files is not.
##### Steps To Reproduce:

Playbooks and associates files are available in - https://github.com/j-manu/debug/tree/master/ansible

Run `ansible-playbook -i inventory site.yml`
##### Expected Results:

```
PLAY [all] ********************************************************************

TASK: [Access var from vars list] *********************************************
ok: [localhost] => {
    "msg": "App version is 1.1"
}

TASK: [Access var from included vars file] ************************************
ok: [localhost] => {
    "msg": "App name is Debug App"
}

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```
##### Actual Results:

```
PLAY [all] ********************************************************************

TASK: [Access var from vars list] *********************************************
ok: [localhost] => {
    "msg": "App version is 1.1"
}

TASK: [Access var from included vars file] ************************************
fatal: [localhost] => One or more undefined variables: 'app_name' is undefined

FATAL: all hosts have already failed -- aborting

PLAY RECAP ********************************************************************
           to retry, use: --limit @/Users/manu/site.retry

localhost                  : ok=1    changed=0    unreachable=1    failed=0
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py
--- a/lib/ansible/playbook/__init__.py
+++ b/lib/ansible/playbook/__init__.py
@@ -179,7 +179,46 @@ def __init__(self,
 
     # *****************************************************
 
-    def _load_playbook_from_file(self, path, vars={}):
+    def _get_playbook_vars(self, play_ds, existing_vars):
+        '''
+        Gets the vars specified with the play and blends them 
+        with any existing vars that have already been read in
+        '''
+        new_vars = existing_vars.copy()
+        if 'vars' in play_ds:
+            if isinstance(play_ds['vars'], dict):
+                new_vars.update(play_ds['vars'])
+            elif isinstance(play_ds['vars'], list):
+                for v in play_ds['vars']:
+                    new_vars.update(v)
+        return new_vars
+
+    # *****************************************************
+
+    def _get_include_info(self, play_ds, basedir, existing_vars={}):
+        '''
+        Gets any key=value pairs specified with the included file
+        name and returns the merged vars along with the path
+        '''
+        new_vars = existing_vars.copy()
+        tokens = shlex.split(play_ds.get('include', ''))
+        for t in tokens[1:]:
+            (k,v) = t.split("=", 1)
+            new_vars[k] = template(basedir, v, new_vars)
+
+        return (new_vars, tokens[0])
+
+    # *****************************************************
+
+    def _get_playbook_vars_files(self, play_ds, existing_vars_files):
+        new_vars_files = list(existing_vars_files)
+        if 'vars_files' in play_ds:
+            new_vars_files = utils.list_union(new_vars_files, play_ds['vars_files'])
+        return new_vars_files
+
+    # *****************************************************
+
+    def _load_playbook_from_file(self, path, vars={}, vars_files=[]):
         '''
         run top level error checking on playbooks and allow them to include other playbooks.
         '''
@@ -201,36 +240,25 @@ def _load_playbook_from_file(self, path, vars={}):
                 # a playbook (list of plays) decided to include some other list of plays
                 # from another file.  The result is a flat list of plays in the end.
 
-                tokens = shlex.split(play['include'])
-
-                incvars = vars.copy()
-                if 'vars' in play:
-                    if isinstance(play['vars'], dict):
-                        incvars.update(play['vars'])
-                    elif isinstance(play['vars'], list):
-                        for v in play['vars']:
-                            incvars.update(v)
-
-                # allow key=value parameters to be specified on the include line
-                # to set variables
-
-                for t in tokens[1:]:
-
-                    (k,v) = t.split("=", 1)
-                    incvars[k] = template(basedir, v, incvars)
+                play_vars = self._get_playbook_vars(play, vars)
+                play_vars_files = self._get_playbook_vars_files(play, vars_files)
+                inc_vars, inc_path = self._get_include_info(play, basedir, play_vars)
+                play_vars.update(inc_vars)
 
-                included_path = utils.path_dwim(basedir, template(basedir, tokens[0], incvars))
-                (plays, basedirs) = self._load_playbook_from_file(included_path, incvars)
+                included_path = utils.path_dwim(basedir, template(basedir, inc_path, play_vars))
+                (plays, basedirs) = self._load_playbook_from_file(included_path, vars=play_vars, vars_files=play_vars_files)
                 for p in plays:
                     # support for parameterized play includes works by passing
                     # those variables along to the subservient play
                     if 'vars' not in p:
                         p['vars'] = {}
                     if isinstance(p['vars'], dict):
-                        p['vars'].update(incvars)
+                        p['vars'].update(play_vars)
                     elif isinstance(p['vars'], list):
                         # nobody should really do this, but handle vars: a=1 b=2
-                        p['vars'].extend([{k:v} for k,v in incvars.iteritems()])
+                        p['vars'].extend([{k:v} for k,v in play_vars.iteritems()])
+                    # now add in the vars_files
+                    p['vars_files'] = utils.list_union(p.get('vars_files', []), play_vars_files)
 
                 accumulated_plays.extend(plays)
                 play_basedirs.extend(basedirs)
diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py
--- a/lib/ansible/utils/__init__.py
+++ b/lib/ansible/utils/__init__.py
@@ -1005,6 +1005,22 @@ def is_list_of_strings(items):
             return False
     return True
 
+def _listify(a):
+    if not isinstance(a, (list, tuple)):
+        return [a,]
+    else:
+        return a
+
+def list_union(a, b):
+    set_a = set(_listify(a))
+    set_b = set(_listify(b))
+    return list(set_a.union(set_b))
+
+def list_intersection(a, b):
+    set_a = set(_listify(a))
+    set_b = set(_listify(b))
+    return list(set_a.intersection(set_b))
+
 def safe_eval(expr, locals={}, include_exceptions=False):
     '''
     this is intended for allowing things like:


Test patch: