Repository: ansible/ansible

Issue:
Vars should also be kept per task
This currently fails:

``` yaml
- hosts: rh5man
  tasks:
  - include: networking.yml interface="eth0" mac="52:54:00:38:CC:5A" ipaddr="192.168.122.35" netmask="255.255.255.0"
  - include: networking.yml interface="eth1" mac="12:34:56:78:90:12" ipaddr="127.0.0.2" netmask="255.255.255.255"
```

With networking.yml:

``` yaml

---
- name: update network interface {{interface}}
  action: template src=res/ifcfg dest=/etc/sysconfig/network-scripts/ifcfg-{{ interface }}
```

It results in:

``` bash
[root@rh5man ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth1
BOOTPROTO=static
DHCPCLASS=
HWADDR=12:34:56:78:90:12
IPADDR=127.0.0.2
NETMASK=255.255.255.255
ONBOOT=yes
```

With ifcfg-eth1 identical.

This is because the vars are stored per play. In this case you need vars per task.

Note that substitution in the playbook is correct because the substitutions for eth0 are made before the variables of eth1 are read.



Discussion:

Resolving patch:
diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py
--- a/lib/ansible/playbook.py
+++ b/lib/ansible/playbook.py
@@ -113,14 +113,19 @@ def _include_tasks(self, play, task, dirname, new_tasks):
         # include: some.yml a=2 b=3 c=4
         include_tokens = task['include'].split()
         path = utils.path_dwim(dirname, include_tokens[0])
-        inject_vars = self._get_vars(play, dirname)
+        play_vars = self._get_vars(play, dirname)
+        include_vars = {}
         for i,x in enumerate(include_tokens):
             if x.find("=") != -1:
                 (k,v) = x.split("=")
-                inject_vars[k] = v
+                include_vars[k] = v
+        inject_vars = play_vars.copy()
+        inject_vars.update(include_vars)
         included = utils.template_from_file(path, inject_vars)
         included = utils.parse_yaml(included)
         for x in included:
+            if len(include_vars):
+                x["vars"] = include_vars
             new_tasks.append(x)
 
     # *****************************************************
@@ -266,7 +271,7 @@ def _async_poll(self, runner, hosts, async_seconds, async_poll_interval, only_if
 
     # *****************************************************
 
-    def _run_module(self, pattern, host_list, module, args, remote_user, 
+    def _run_module(self, pattern, host_list, module, args, vars, remote_user, 
         async_seconds, async_poll_interval, only_if, sudo):
         ''' run a particular module step in a playbook '''
 
@@ -277,7 +282,7 @@ def _run_module(self, pattern, host_list, module, args, remote_user,
             module_args=args, host_list=hosts, forks=self.forks,
             remote_pass=self.remote_pass, module_path=self.module_path,
             timeout=self.timeout, remote_user=remote_user, 
-            remote_port=self.remote_port,
+            remote_port=self.remote_port, module_vars=vars,
             setup_cache=SETUP_CACHE, basedir=self.basedir,
             conditional=only_if, callbacks=self.runner_callbacks, 
             extra_vars=self.extra_vars, debug=self.debug, sudo=sudo
@@ -309,6 +314,9 @@ def _run_task(self, pattern=None, host_list=None, task=None,
         module_name = tokens[0]
         module_args = tokens[1]
 
+        # include task specific vars
+        module_vars = task.get('vars')
+
         # tasks can be direct (run on all nodes matching
         # the pattern) or conditional, where they ran
         # as the result of a change handler on a subset
@@ -319,7 +327,7 @@ def _run_task(self, pattern=None, host_list=None, task=None,
         # load up an appropriate ansible runner to
         # run the task in parallel
         results = self._run_module(pattern, host_list, module_name, 
-            module_args, remote_user, async_seconds, 
+            module_args, module_vars, remote_user, async_seconds, 
             async_poll_interval, only_if, sudo)
 
         self.stats.compute(results)
diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py
--- a/lib/ansible/runner.py
+++ b/lib/ansible/runner.py
@@ -74,8 +74,8 @@ def __init__(self, host_list=C.DEFAULT_HOST_LIST, module_path=C.DEFAULT_MODULE_P
         remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS,
         remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None,
         transport='paramiko', conditional='True', groups={}, callbacks=None, verbose=False,
-        debug=False, sudo=False, extra_vars=None):
-    
+        debug=False, sudo=False, extra_vars=None, module_vars=None):
+   
         if setup_cache is None:
             setup_cache = {}
         if basedir is None: 
@@ -101,6 +101,7 @@ def __init__(self, host_list=C.DEFAULT_HOST_LIST, module_path=C.DEFAULT_MODULE_P
         self.forks       = int(forks)
         self.pattern     = pattern
         self.module_args = module_args
+        self.module_vars = module_vars
         self.extra_vars  = extra_vars
         self.timeout     = timeout
         self.debug       = debug
@@ -267,7 +268,7 @@ def _transfer_module(self, conn, tmp, module):
 
     # *****************************************************
 
-    def _transfer_argsfile(self, conn, tmp, args_str):
+    def _transfer_str(self, conn, tmp, name, args_str):
         ''' transfer arguments as a single file to be fed to the module. '''
 
         args_fd, args_file = tempfile.mkstemp()
@@ -276,7 +277,7 @@ def _transfer_argsfile(self, conn, tmp, args_str):
         args_fo.flush()
         args_fo.close()
 
-        args_remote = os.path.join(tmp, 'arguments')
+        args_remote = os.path.join(tmp, name)
         conn.put_file(args_file, args_remote)
         os.unlink(args_file)
 
@@ -355,7 +356,7 @@ def _execute_module(self, conn, tmp, remote_module_path, args,
         module_name_tail = remote_module_path.split("/")[-1]
         client_executed_str = "%s %s" % (module_name_tail, args.strip())
 
-        argsfile = self._transfer_argsfile(conn, tmp, args)
+        argsfile = self._transfer_str(conn, tmp, 'arguments', args)
         if async_jid is None:
             cmd = "%s %s" % (remote_module_path, argsfile)
         else:
@@ -498,8 +499,16 @@ def _execute_template(self, conn, host, tmp):
         # install the template module
         template_module = self._transfer_module(conn, tmp, 'template')
 
+        # transfer module vars
+        if self.module_vars:
+            vars = utils.bigjson(self.module_vars)
+            vars_path = self._transfer_str(conn, tmp, 'module_vars', vars)
+            vars_arg=" vars=%s"%(vars_path)
+        else:
+            vars_arg=""
+        
         # run the template module
-        args = "src=%s dest=%s metadata=%s" % (temppath, dest, metadata)
+        args = "src=%s dest=%s metadata=%s%s" % (temppath, dest, metadata, vars_arg)
         (result1, err, executed) = self._execute_module(conn, tmp, template_module, args)
         (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed)
 
@@ -637,13 +646,13 @@ def _parallel_exec(self, hosts):
             prc.start()
             workers.append(prc)
 
-            try:
-                for worker in workers:
-                    worker.join()
-            except KeyboardInterrupt:
-                for worker in workers:
-                    worker.terminate()
-                    worker.join()
+        try:
+            for worker in workers:
+                worker.join()
+        except KeyboardInterrupt:
+            for worker in workers:
+                worker.terminate()
+                worker.join()
 
         results = []
         while not result_queue.empty():


Test patch: