Repository: ansible/ansible

Issue:
Retry with synchronize uses wrong source path on subsequent attempts after failure
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
task_executor

##### ANSIBLE VERSION
```
ansible 2.3.0 (devel d4ac0bdea9) last updated 2016/11/01 06:54:17 (GMT -700)
  lib/ansible/modules/core: (detached HEAD 679a0ae5e9) last updated 2016/11/01 06:54:24 (GMT -700)
  lib/ansible/modules/extras: (detached HEAD cf524673e1) last updated 2016/11/01 06:54:25 (GMT -700)
  config file = /tmp/ans3/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
default

##### OS / ENVIRONMENT
Ubuntu 14.04.5 LTS

##### SUMMARY
Using a retry loop with synchronize results in an incorrect source path being used in subsequent rsync invocations after a failure.

##### STEPS TO REPRODUCE
```
- hosts: all
  tasks:
  - delay: 2
    register: task_result
    retries: 1
    until: task_result.rc == 0
    synchronize:
      dest: /tmp/out
      mode: pull
      src: /tmp/nonexistent/
```

##### EXPECTED RESULTS
The task should have failed, but used the same src path with both attempts.

##### ACTUAL RESULTS
```
Loading callback plugin default of type stdout, v2.0 from /home/corvus/git/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: post_playbook ***********************************************************************************************************************************
1 plays in post_playbook

PLAY [all] ************************************************************************************************************************************************

TASK [setup] **********************************************************************************************************************************************
Using module file /home/corvus/git/ansible/lib/ansible/modules/core/system/setup.py
<zuultest> ESTABLISH SSH CONNECTION FOR USER: zuul
<zuultest> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=zuul -o ConnectTimeout=10 -o ControlPath=/home/corvus/.ansible/cp/ansible-ssh-%h-%p-%r zuultest '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696 `" && echo ansible-tmp-1478009110.81-247288043154696="` echo $HOME/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696 `" ) && sleep 0'"'"''
<zuultest> PUT /tmp/tmpFJD8bL TO /home/zuul/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696/setup.py
<zuultest> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=zuul -o ConnectTimeout=10 -o ControlPath=/home/corvus/.ansible/cp/ansible-ssh-%h-%p-%r '[zuultest]'
<zuultest> ESTABLISH SSH CONNECTION FOR USER: zuul
<zuultest> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=zuul -o ConnectTimeout=10 -o ControlPath=/home/corvus/.ansible/cp/ansible-ssh-%h-%p-%r zuultest '/bin/sh -c '"'"'chmod u+x /home/zuul/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696/ /home/zuul/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696/setup.py && sleep 0'"'"''
<zuultest> ESTABLISH SSH CONNECTION FOR USER: zuul
<zuultest> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=zuul -o ConnectTimeout=10 -o ControlPath=/home/corvus/.ansible/cp/ansible-ssh-%h-%p-%r -tt zuultest '/bin/sh -c '"'"'/usr/bin/python /home/zuul/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696/setup.py; rm -rf "/home/zuul/.ansible/tmp/ansible-tmp-1478009110.81-247288043154696/" > /dev/null 2>&1 && sleep 0'"'"''
ok: [node]

TASK [synchronize] ****************************************************************************************************************************************
task path: /tmp/ans3/ansible/post_playbook:3
Using module file /home/corvus/git/ansible/lib/ansible/modules/core/files/synchronize.py
<zuultest> ESTABLISH LOCAL CONNECTION FOR USER: corvus
<zuultest> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739 `" && echo ansible-tmp-1478009111.1-50376276516739="` echo $HOME/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739 `" ) && sleep 0'
<zuultest> PUT /tmp/tmpbAMfgH TO /home/corvus/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739/synchronize.py
<zuultest> EXEC /bin/sh -c 'chmod u+x /home/corvus/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739/ /home/corvus/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739/synchronize.py && sleep 0'
<zuultest> EXEC /bin/sh -c '/tmp/ansenv/bin/python /home/corvus/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739/synchronize.py; rm -rf "/home/corvus/.ansible/tmp/ansible-tmp-1478009111.1-50376276516739/" > /dev/null 2>&1 && sleep 0'
FAILED - RETRYING: TASK: synchronize (1 retries left).Result was: {
    "attempts": 1, 
    "cmd": "/usr/bin/rsync --delay-updates -F --compress --archive --rsh 'ssh  -S none -o StrictHostKeyChecking=no' --out-format='<<CHANGED>>%i %n%L' \"zuul@zuultest:/tmp/nonexistent/\" \"/tmp/out\"", 
    "failed": true, 
    "invocation": {
        "module_args": {
            "_local_rsync_path": "rsync", 
            "_substitute_controller": false, 
            "archive": true, 
            "checksum": false, 
            "compress": true, 
            "copy_links": null, 
            "delete": false, 
            "dest": "/tmp/out", 
            "dest_port": null, 
            "dirs": false, 
            "existing_only": false, 
            "group": null, 
            "links": null, 
            "mode": "pull", 
            "owner": null, 
            "partial": false, 
            "perms": null, 
            "private_key": null, 
            "recursive": null, 
            "rsync_opts": null, 
            "rsync_path": null, 
            "rsync_timeout": 0, 
            "set_remote_user": true, 
            "src": "zuul@zuultest:/tmp/nonexistent/", 
            "ssh_args": null, 
            "times": null, 
            "verify_host": false
        }
    }, 
    "msg": "rsync: change_dir \"/tmp/nonexistent\" failed: No such file or directory (2)\nrsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1655) [Receiver=3.1.0]\nrsync: [Receiver] write error: Broken pipe (32)\n", 
    "rc": 23, 
    "retries": 2
}
Using module file /home/corvus/git/ansible/lib/ansible/modules/core/files/synchronize.py
<zuultest> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331 `" && echo ansible-tmp-1478009113.83-242513217428331="` echo $HOME/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331 `" ) && sleep 0'
<zuultest> PUT /tmp/tmpf9NXng TO /home/corvus/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331/synchronize.py
<zuultest> EXEC /bin/sh -c 'chmod u+x /home/corvus/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331/ /home/corvus/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331/synchronize.py && sleep 0'
<zuultest> EXEC /bin/sh -c '/tmp/ansenv/bin/python /home/corvus/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331/synchronize.py; rm -rf "/home/corvus/.ansible/tmp/ansible-tmp-1478009113.83-242513217428331/" > /dev/null 2>&1 && sleep 0'
fatal: [node]: FAILED! => {
    "attempts": 1, 
    "changed": false, 
    "cmd": "/usr/bin/rsync --delay-updates -F --compress --archive --rsh 'ssh  -S none -o StrictHostKeyChecking=no' --out-format='<<CHANGED>>%i %n%L' \"/tmp/ans3/ansible/zuul@zuultest:/tmp/nonexistent/\" \"/tmp/out\"", 
    "failed": true, 
    "invocation": {
        "module_args": {
            "_local_rsync_path": "rsync", 
            "_substitute_controller": false, 
            "archive": true, 
            "checksum": false, 
            "compress": true, 
            "copy_links": null, 
            "delete": false, 
            "dest": "/tmp/out", 
            "dest_port": null, 
            "dirs": false, 
            "existing_only": false, 
            "group": null, 
            "links": null, 
            "mode": "pull", 
            "owner": null, 
            "partial": false, 
            "perms": null, 
            "private_key": null, 
            "recursive": null, 
            "rsync_opts": null, 
            "rsync_path": null, 
            "rsync_timeout": 0, 
            "set_remote_user": true, 
            "src": "/tmp/ans3/ansible/zuul@zuultest:/tmp/nonexistent/", 
            "ssh_args": null, 
            "times": null, 
            "verify_host": false
        }
    }, 
    "msg": "rsync: change_dir \"/tmp/ans3/ansible/zuul@zuultest:/tmp/nonexistent\" failed: No such file or directory (2)\nrsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.0]\n", 
    "rc": 23
}
	to retry, use: --limit @/tmp/ans3/ansible/post_playbook.retry

PLAY RECAP ************************************************************************************************************************************************
node                       : ok=1    changed=0    unreachable=0    failed=1   
```

Note specifically that the first attempt used the source path:

    zuul@zuultest:/tmp/nonexistent/

While the second used the nonsensical:

    /tmp/ans3/ansible/zuul@zuultest:/tmp/nonexistent/



Discussion:
I'm betting this is the issue: https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/action/synchronize.py#L313

Looks like the fix would be:
- copy self._task.args early in the run() method.
- anytime we modify _task.args in the current code, modify the copy instead.
- When we invoke _execute_module(), pass it the copy of _task.args so that it uses that instead of falling back to self._task.args.


Resolving patch:
diff --git a/lib/ansible/plugins/action/synchronize.py b/lib/ansible/plugins/action/synchronize.py
--- a/lib/ansible/plugins/action/synchronize.py
+++ b/lib/ansible/plugins/action/synchronize.py
@@ -74,7 +74,7 @@ def _process_origin(self, host, path, user):
             path = self._get_absolute_path(path=path)
         return path
 
-    def _process_remote(self, host, path, user, port_matches_localhost_port):
+    def _process_remote(self, task_args, host, path, user, port_matches_localhost_port):
         """
         :arg host: hostname for the path
         :arg path: file path
@@ -97,7 +97,7 @@ def _process_remote(self, host, path, user, port_matches_localhost_port):
             # fix up the rsync path to use the controller's public DNS/IP
             # instead of "localhost"
             if port_matches_localhost_port and host in C.LOCALHOST:
-                self._task.args['_substitute_controller'] = True
+                task_args['_substitute_controller'] = True
             return self._format_rsync_rsh_target(host, path, user)
 
         if ':' not in path and not path.startswith('/'):
@@ -155,6 +155,11 @@ def run(self, tmp=None, task_vars=None):
         if task_vars is None:
             task_vars = dict()
 
+        # We make a copy of the args here because we may fail and be asked to
+        # retry. If that happens we don't want to pass the munged args through
+        # to our next invocation. Munged args are single use only.
+        _tmp_args = self._task.args.copy()
+
         result = super(ActionModule, self).run(tmp, task_vars)
 
         # self._connection accounts for delegate_to so
@@ -178,10 +183,10 @@ def run(self, tmp=None, task_vars=None):
             result['msg'] = "synchronize uses rsync to function. rsync needs to connect to the remote host via ssh or a direct filesystem copy. This remote host is being accessed via %s instead so it cannot work." % self._connection.transport
             return result
 
-        use_ssh_args = self._task.args.pop('use_ssh_args', None)
+        use_ssh_args = _tmp_args.pop('use_ssh_args', None)
 
         # Parameter name needed by the ansible module
-        self._task.args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync'
+        _tmp_args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync'
 
         # rsync thinks that one end of the connection is localhost and the
         # other is the host we're running the task for  (Note: We use
@@ -218,9 +223,9 @@ def run(self, tmp=None, task_vars=None):
 
         # CHECK FOR NON-DEFAULT SSH PORT
         inv_port = task_vars.get('ansible_ssh_port', None) or C.DEFAULT_REMOTE_PORT
-        if self._task.args.get('dest_port', None) is None:
+        if _tmp_args.get('dest_port', None) is None:
             if inv_port is not None:
-                self._task.args['dest_port'] = inv_port
+                _tmp_args['dest_port'] = inv_port
 
         # Set use_delegate if we are going to run rsync on a delegated host
         # instead of localhost
@@ -262,12 +267,12 @@ def run(self, tmp=None, task_vars=None):
             self._override_module_replaced_vars(task_vars)
 
         # SWITCH SRC AND DEST HOST PER MODE
-        if self._task.args.get('mode', 'push') == 'pull':
+        if _tmp_args.get('mode', 'push') == 'pull':
             (dest_host, src_host) = (src_host, dest_host)
 
         # MUNGE SRC AND DEST PER REMOTE_HOST INFO
-        src = self._task.args.get('src', None)
-        dest = self._task.args.get('dest', None)
+        src = _tmp_args.get('src', None)
+        dest = _tmp_args.get('dest', None)
         if src is None or dest is None:
             return dict(failed=True,
                     msg="synchronize requires both src and dest parameters are set")
@@ -278,12 +283,12 @@ def run(self, tmp=None, task_vars=None):
 
             if private_key is not None:
                 private_key = os.path.expanduser(private_key)
-                self._task.args['private_key'] = private_key
+                _tmp_args['private_key'] = private_key
 
             # Src and dest rsync "path" handling
             # Determine if we need a user@
             user = None
-            if boolean(self._task.args.get('set_remote_user', 'yes')):
+            if boolean(_tmp_args.get('set_remote_user', 'yes')):
                 if use_delegate:
                     user = task_vars.get('ansible_delegated_vars', dict()).get('ansible_ssh_user', None)
                     if not user:
@@ -293,14 +298,14 @@ def run(self, tmp=None, task_vars=None):
                     user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user
 
             # use the mode to define src and dest's url
-            if self._task.args.get('mode', 'push') == 'pull':
+            if _tmp_args.get('mode', 'push') == 'pull':
                 # src is a remote path: <user>@<host>, dest is a local path
-                src = self._process_remote(src_host, src, user, inv_port in localhost_ports)
+                src = self._process_remote(_tmp_args, src_host, src, user, inv_port in localhost_ports)
                 dest = self._process_origin(dest_host, dest, user)
             else:
                 # src is a local path, dest is a remote path: <user>@<host>
                 src = self._process_origin(src_host, src, user)
-                dest = self._process_remote(dest_host, dest, user, inv_port in localhost_ports)
+                dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports)
         else:
             # Still need to munge paths (to account for roles) even if we aren't
             # copying files between hosts
@@ -309,11 +314,11 @@ def run(self, tmp=None, task_vars=None):
             if not dest.startswith('/'):
                 dest = self._get_absolute_path(path=dest)
 
-        self._task.args['src'] = src
-        self._task.args['dest'] = dest
+        _tmp_args['src'] = src
+        _tmp_args['dest'] = dest
 
         # Allow custom rsync path argument
-        rsync_path = self._task.args.get('rsync_path', None)
+        rsync_path = _tmp_args.get('rsync_path', None)
 
         if not dest_is_local:
             if self._play_context.become and not rsync_path:
@@ -330,7 +335,7 @@ def run(self, tmp=None, task_vars=None):
 
         # make sure rsync path is quoted.
         if rsync_path:
-            self._task.args['rsync_path'] = '"%s"' % rsync_path
+            _tmp_args['rsync_path'] = '"%s"' % rsync_path
 
         if use_ssh_args:
             ssh_args = [
@@ -338,10 +343,10 @@ def run(self, tmp=None, task_vars=None):
                 getattr(self._play_context, 'ssh_common_args', ''),
                 getattr(self._play_context, 'ssh_extra_args', ''),
             ]
-            self._task.args['ssh_args'] = ' '.join([a for a in ssh_args if a])
+            _tmp_args['ssh_args'] = ' '.join([a for a in ssh_args if a])
 
         # run the module and store the result
-        result.update(self._execute_module('synchronize', task_vars=task_vars))
+        result.update(self._execute_module('synchronize', module_args=_tmp_args, task_vars=task_vars))
 
         if 'SyntaxError' in result.get('exception', result.get('msg', '')):
             # Emit a warning about using python3 because synchronize is


Test patch:
diff --git a/test/units/plugins/action/fixtures/synchronize/basic/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic/meta.yaml
@@ -12,6 +12,6 @@ asserts:
     - "SAM._connection.transport == 'local'"
     - "self._play_context.shell == 'sh'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'root@el6host:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'root@el6host:/tmp/deleteme'"
diff --git a/test/units/plugins/action/fixtures/synchronize/basic_become/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic_become/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic_become/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic_become/meta.yaml
@@ -23,11 +23,11 @@ asserts:
     - "hasattr(SAM._connection, 'ismock')"
     - "SAM._connection.transport == 'local'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
     # this is a crucial aspect of this scenario ...
-    - "self.task.args['rsync_path'] == '\"sudo rsync\"'"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'root@el6host:/tmp/deleteme'"
+    - "self.final_module_args['rsync_path'] == '\"sudo rsync\"'"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'root@el6host:/tmp/deleteme'"
     - "self.task.become == True"
     - "self.task.become_user == None"
     - "self._play_context.shell == 'sh'"
diff --git a/test/units/plugins/action/fixtures/synchronize/basic_become_cli/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic_become_cli/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic_become_cli/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic_become_cli/meta.yaml
@@ -23,11 +23,11 @@ asserts:
     - "hasattr(SAM._connection, 'ismock')"
     - "SAM._connection.transport == 'local'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
     # this is a crucial aspect of this scenario ...
-    - "self.task.args['rsync_path'] == '\"sudo rsync\"'"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'root@el6host:/tmp/deleteme'"
+    - "self.final_module_args['rsync_path'] == '\"sudo rsync\"'"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'root@el6host:/tmp/deleteme'"
     - "self.task.become == None"
     - "self.task.become_user == None"
     - "self._play_context.shell == 'sh'"
diff --git a/test/units/plugins/action/fixtures/synchronize/basic_vagrant/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic_vagrant/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic_vagrant/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic_vagrant/meta.yaml
@@ -17,10 +17,10 @@ asserts:
     - "hasattr(SAM._connection, 'ismock')"
     - "SAM._connection.transport == 'local'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['dest_port'] == 2202"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['dest_port'] == 2202"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
     - "self._play_context.shell == 'sh'"
     - "self._play_context.remote_addr == '127.0.0.1'"
     - "self._play_context.remote_user == 'vagrant'"
diff --git a/test/units/plugins/action/fixtures/synchronize/basic_vagrant_become_cli/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic_vagrant_become_cli/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic_vagrant_become_cli/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic_vagrant_become_cli/meta.yaml
@@ -20,10 +20,10 @@ asserts:
     - "hasattr(SAM._connection, 'ismock')"
     - "SAM._connection.transport == 'local'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['dest_port'] == 2202"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['dest_port'] == 2202"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
     - "self._play_context.shell == 'sh'"
     - "self._play_context.remote_addr == '127.0.0.1'"
     - "self._play_context.remote_user == 'vagrant'"
diff --git a/test/units/plugins/action/fixtures/synchronize/basic_vagrant_sudo/meta.yaml b/test/units/plugins/action/fixtures/synchronize/basic_vagrant_sudo/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/basic_vagrant_sudo/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/basic_vagrant_sudo/meta.yaml
@@ -17,10 +17,10 @@ asserts:
     - "hasattr(SAM._connection, 'ismock')"
     - "SAM._connection.transport == 'local'"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['dest_port'] == 2202"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['dest_port'] == 2202"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'vagrant@127.0.0.1:/tmp/deleteme'"
     - "self._play_context.shell == 'sh'"
     - "self._play_context.remote_addr == '127.0.0.1'"
     - "self._play_context.remote_user == 'vagrant'"
diff --git a/test/units/plugins/action/fixtures/synchronize/delegate_remote/meta.yaml b/test/units/plugins/action/fixtures/synchronize/delegate_remote/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/delegate_remote/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/delegate_remote/meta.yaml
@@ -21,6 +21,6 @@ asserts:
     - "SAM._connection.transport == 'ssh'"
     - "self._play_context.shell == None"
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'el6host:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'el6host:/tmp/deleteme'"
diff --git a/test/units/plugins/action/fixtures/synchronize/delegate_remote_su/meta.yaml b/test/units/plugins/action/fixtures/synchronize/delegate_remote_su/meta.yaml
--- a/test/units/plugins/action/fixtures/synchronize/delegate_remote_su/meta.yaml
+++ b/test/units/plugins/action/fixtures/synchronize/delegate_remote_su/meta.yaml
@@ -28,6 +28,6 @@ asserts:
     - "not self._play_context.become"            
     - "self._play_context.become_method == 'su'"            
     - "self.execute_called"
-    - "self.task.args['_local_rsync_path'] == 'rsync'"
-    - "self.task.args['src'] == '/tmp/deleteme'"
-    - "self.task.args['dest'] == 'el6host:/tmp/deleteme'"
+    - "self.final_module_args['_local_rsync_path'] == 'rsync'"
+    - "self.final_module_args['src'] == '/tmp/deleteme'"
+    - "self.final_module_args['dest'] == 'el6host:/tmp/deleteme'"
diff --git a/test/units/plugins/action/test_synchronize.py b/test/units/plugins/action/test_synchronize.py
--- a/test/units/plugins/action/test_synchronize.py
+++ b/test/units/plugins/action/test_synchronize.py
@@ -99,11 +99,12 @@ class SynchronizeTester(object):
     execute_called = False
 
 
-    def _execute_module(self, module_name, task_vars=None):
+    def _execute_module(self, module_name, module_args=None, task_vars=None):
         self.execute_called = True
+        self.final_module_args = module_args
         self.final_task_vars = task_vars
         return {}
-    
+
     def runtest(self, fixturepath='fixtures/synchronize/basic'):
 
         metapath = os.path.join(fixturepath, 'meta.yaml')