Repository: ansible/ansible

Issue:
synchronize broken in 2.3
##### ISSUE TYPE

- Bug Report

##### COMPONENT NAME

* synchronize

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible --version
ansible 2.3.0.0
  config file =
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, May  4 2017, 15:45:50) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT

* Mac OS
* Travis CI Standard Linux

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE

```yaml
- name: Copy binaries from archive into etcd dir
  synchronize: src="{{ etcd_download_dir }}/{{ etcd_release }}/{{ item }}"
                         dest="{{ etcd_dir }}"
                         archive=True
  delegate_to: "{{ inventory_hostname }}"
  with_items:
    - etcd
    - etcdctl
  when: not etcd_config_only|bool
```

##### EXPECTED RESULTS

Works perfectly fine against 2.2 Ansible.

```
    changed: [etcd-03-default -> etcd-03-default] => (item=etcd)
    changed: [etcd-01-default -> etcd-01-default] => (item=etcd)
    changed: [etcd-02-default -> etcd-02-default] => (item=etcd)
    changed: [etcd-03-default -> etcd-03-default] => (item=etcdctl)
    changed: [etcd-01-default -> etcd-01-default] => (item=etcdctl)
    changed: [etcd-02-default -> etcd-02-default] => (item=etcdctl)
```
##### ACTUAL RESULTS

```
    An exception occurred during task execution. To see the full traceback, use -vvv. The error was: UnboundLocalError: local variable 'user' referenced before assignment
    fatal: [etcd-03-default]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}
```

Local Inventory:

```yaml
---
all:
  hosts:
    etcd-01-default: &id001
      ansible_connection: docker
    etcd-02-default: &id002
      ansible_connection: docker
    etcd-03-default: &id003
      ansible_connection: docker
etcd:
  hosts:
    etcd-01-default: *id001
    etcd-02-default: *id002
    etcd-03-default: *id003
ungrouped:
  vars: {}
```


Discussion:

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
@@ -311,17 +311,10 @@ def run(self, tmp=None, task_vars=None):
             return dict(failed=True,
                     msg="synchronize requires both src and dest parameters are set")
 
+        # Determine if we need a user@
+        user = None
         if not dest_is_local:
-            # Private key handling
-            private_key = self._play_context.private_key_file
-
-            if private_key is not None:
-                private_key = os.path.expanduser(private_key)
-                _tmp_args['private_key'] = private_key
-
             # Src and dest rsync "path" handling
-            # Determine if we need a user@
-            user = None
             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)
@@ -331,6 +324,13 @@ def run(self, tmp=None, task_vars=None):
                 else:
                     user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user
 
+            # Private key handling
+            private_key = self._play_context.private_key_file
+
+            if private_key is not None:
+                private_key = os.path.expanduser(private_key)
+                _tmp_args['private_key'] = private_key
+
             # use the mode to define src and dest's url
             if _tmp_args.get('mode', 'push') == 'pull':
                 # src is a remote path: <user>@<host>, dest is a local path


Test patch: