Repository: ansible/ansible

Issue:
Synchronize module try to copy files to host machine when using Vagrant
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### ANSIBLE VERSION

```
ansible 2.0.2.0
```
##### CONFIGURATION

Default from Homebrew
##### OS / ENVIRONMENT

<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->

Host: OS X El Capital 10.11.4
Managing OS: Ubuntu 14.04 Vagrant VM using box `ubuntu/trusty64`
##### SUMMARY

<!--- Explain the problem briefly -->

I used `synchronize` module and expected it to copy files from my machine to the vm but actually, it tried to copy files onto my own machine (same machine that ran ansible)
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

After use `vagrant up` it will fail on `synchronize` module

my Vagrant

```
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"

  config.vm.network :private_network, ip: "192.168.20.10"
  config.vm.network :forwarded_port, guest: 80, host: 8088

  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--name", "portal", "--memory", "1024"]
  end

  config.vm.provision :ansible do |ansible|
    ansible.playbook = "path/to/my/playbook.yaml"
    ansible.host_key_checking = false
    ansible.verbose = "vv"
    ansible.vault_password_file = "vault_password"
  end

end

```

my playbook

```

---
- hosts: all
  become: yes
  remote_user: vagrant

  vars_files:
    - my_var_file.yml

  vars:
    my_var: some_var

  roles:
    - my_role
```

step that it failed

```

---
- name: create a project directory
  file: path={{ PROJECT_PATH }}
        state=directory
        mode=0777

- name: copy local project files onto server
  synchronize:
    src: '{{ playbook_dir }}/../'
    dest: '{{ PROJECT_PATH }}'
    recursive: yes
    checksum: yes
    perms: no
    rsync_opts:
      - --omit-dir-times
      - --exclude=staticfiles
      - --exclude=__pycache__
      - --exclude=.git
      - --exclude=.vagrant
      - --exclude=.idea
  become: false

- name: correct directory permission
  file: path={{ PROJECT_PATH }}
        state=directory
        recurse=yes
        mode=0755
        owner=root
        group=root
```

notice that I need to use `become: false` in `synchronize` module, since I found out that Ansible still doesn't play nice with `become: yes`. It should be another bug report, but I just want to report this one first.
##### EXPECTED RESULTS

files would be copied to Vagrant vm
##### ACTUAL RESULTS

It tried to copy files onto the host machine

```
ESTABLISH LOCAL CONNECTION FOR USER: Kelvin
127.0.0.1 EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461308271.76-277437490463403 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461308271.76-277437490463403 `" )'
127.0.0.1 PUT /var/folders/nz/kf232n_519qgqmy7x9qh6mjc0000gn/T/tmpKLFEc9 TO /Users/Kelvin/.ansible/tmp/ansible-tmp-1461308271.76-277437490463403/synchronize
127.0.0.1 EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /Users/Kelvin/.ansible/tmp/ansible-tmp-1461308271.76-277437490463403/synchronize; rm -rf "/Users/Kelvin/.ansible/tmp/ansible-tmp-1461308271.76-277437490463403/" > /dev/null 2>&1'
fatal: [default]: FAILED! => {"changed": false, "cmd": "/usr/bin/rsync --delay-updates -F --compress --checksum --archive --no-perms --rsh '/usr/bin/ssh -i /Users/Kelvin/Workspaces/cloud-wifi-portal/.vagrant/machines/default/virtualbox/private_key -S none -o StrictHostKeyChecking=no -o Port=2222' --omit-dir-times --exclude=staticfiles --exclude=__pycache__ --exclude=.git --exclude=.vagrant --exclude=.idea --out-format='<<CHANGED>>%i %n%L' \"/Users/Kelvin/Workspaces/cloud-wifi-portal/deploy/../\" \"/webapps/cloud-wifi-portal\"", "failed": true, "invocation": {"module_args": {"_local_rsync_path": "rsync", "_substitute_controller": false, "archive": true, "checksum": true, "compress": true, "copy_links": null, "delete": false, "dest": "/webapps/cloud-wifi-portal", "dest_port": 2222, "dirs": false, "existing_only": false, "group": null, "links": null, "mode": "push", "owner": null, "partial": false, "perms": false, "private_key": "/Users/Kelvin/Workspaces/cloud-wifi-portal/.vagrant/machines/default/virtualbox/private_key", "recursive": true, "rsync_opts": ["--omit-dir-times", "--exclude=staticfiles", "--exclude=__pycache__", "--exclude=.git", "--exclude=.vagrant", "--exclude=.idea"], "rsync_path": null, "rsync_timeout": 0, "set_remote_user": true, "src": "/Users/Kelvin/Workspaces/cloud-wifi-portal/deploy/../", "ssh_args": null, "times": null, "verify_host": false}}, "msg": "rsync: mkdir \"/webapps/cloud-wifi-portal\" failed: No such file or directory (2)\nrsync error: error in file IO (code 11) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-47/rsync/main.c(544) [receiver=2.6.9]\nrsync: connection unexpectedly closed (8 bytes received so far) [sender]\nrsync error: error in rsync protocol data stream (code 12) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-47/rsync/io.c(453) [sender=2.6.9]\n", "rc": 12}
```

Also I tried running the playbook using `ansible-playbook` with host with the same ip configured in `Vagrant` file, and copied my SSH key onto Vagrant vm before hand. It copied all the files successfully.



Discussion:
not sure that this is an Ansible issue as when you did it manually it worked fine

Need more information about the ansible portion of this.  What's in your inventory file?  which host from the inventory file are you then trying to contact?  You're using the synchronize from a release tarball I take it?

Hmm... I just saw that you said you ran this with ansible-playbook and it worked fine.  Can you confirm I understood you correctly?  If that's right, then this isn't a bug in ansible... you'll probably have to open a bug with vagrant to figure that out.

@abadger Yes you understood it correctly. I'll try to open a bug on Vagrant then.

This seems to still be an issue with Ansible 2.0.2.0 and Vagrant 1.8.1.

The error is something like this:

``` json
{
    "changed": false,
    "cmd": "/usr/bin/rsync --delay-updates -F --compress --delete-after --archive --rsh '/usr/bin/ssh -i /home/project/.vagrant/machines/default/virtualbox/private_key -S none -o StrictHostKeyChecking=no -o Port=2222' --rsync-path=\"sudo rsync\" --exclude 'node_modules' --exclude '.git' --out-format='<<CHANGED>>%i %n%L' \"/home/path/server\" \"/opt\"",
    "failed": true,
    "msg": "rsync: recv_generator: mkdir \"/opt/server\" failed: Permission denied (13)\n*** Skipping any contents from this failed directory ***\nrsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.1]\n",
    "rc": 23
}
```

My play is

```
- name: Deploy the code
  synchronize:
        src: server
        dest: /opt
        delete: yes
        rsync_opts: "--exclude 'node_modules' --exclude '.git'"
```

What's interesting is that if I take the rsync command and run it directly on host I can get the same failure. I then `sudo` run it, and it copies the files to my host and not to the vagrant box.

I can modify the raw rsync command a bit by adding `vagrant@127.0.0.1` and it works:

```
/usr/bin/rsync --delay-updates -F --compress --delete-after --archive --rsh '/usr/bin/ssh -i /home/program/.vagrant/machines/default/virtualbox/private_key -S none -o StrictHostKeyChecking=no -o Port=2222' --rsync-path="sudo rsync" --exclude 'node_modules' --exclude '.git' --out-format='<<CHANGED>>%i %n%L' "/home/program/server" vagrant@127.0.0.1:/opt
```

The workaround solution then becomes:

```
- name: Raw deploy the code
  local_action: command /usr/bin/rsync --delay-updates -F --compress --delete-after --archive --rsh '/usr/bin/ssh -i /home/program/.vagrant/machines/default/virtualbox/private_key -S none -o StrictHostKeyChecking=no -o Port=2222' --rsync-path='sudo rsync' --exclude 'node_modules' --exclude '.git' --out-format='<<CHANGED>>%i %n%L' /home/program/server vagrant@127.0.0.1:/opt
  become: false
```

Obviously this won't work if you want to then use the same playbook to target machines that are not your vagrant dev box, so I think adding a `when` condition should do the trick. Something like this (untested):

```
- name: Deploy the code
  synchronize:
        src: server
        dest: /opt
        delete: yes
        rsync_opts: "--exclude 'node_modules' --exclude '.git'"
  when: ansible_nodename != "vagrant"

- name: Raw deploy the code
  local_action: command /usr/bin/rsync --delay-updates -F --compress --delete-after --archive --rsh '/usr/bin/ssh -i /home/program/.vagrant/machines/default/virtualbox/private_key -S none -o StrictHostKeyChecking=no -o Port=2222' --rsync-path='sudo rsync' --exclude 'node_modules' --exclude '.git' --out-format='<<CHANGED>>%i %n%L' /home/program/server vagrant@127.0.0.1:/opt
  become: false
  when: ansible_nodename == "vagrant"
```

Not the most elegant solution, though.

I patched ansible like this:

``` diff
--- ansible/plugins/action/synchronize.py-v00   2016-05-25 10:00:51.051557331 +0100
+++ ansible/plugins/action/synchronize.py   2016-05-25 10:00:32.908336485 +0100
@@ -81,7 +81,7 @@
             alternative ssh port to a vagrant host.)
         """
         transport = self._connection.transport
-        if host not in C.LOCALHOST or transport != "local":
+        if host not in C.LOCALHOST or transport != "local" or (host in C.LOCALHOST and not port_matches_localhost_port):
             if port_matches_localhost_port and host in C.LOCALHOST:
                 self._task.args['_substitute_controller'] = True
             x=self._format_rsync_rsh_target(host, path, user)
```


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
@@ -85,7 +85,8 @@ def _process_remote(self, host, path, user, port_matches_localhost_port):
             alternative ssh port to a vagrant host.)
         """
         transport = self._connection.transport
-        if host not in C.LOCALHOST or transport != "local":
+        if host not in C.LOCALHOST or transport != "local" or \
+                (host in C.LOCALHOST and not port_matches_localhost_port):
             if port_matches_localhost_port and host in C.LOCALHOST:
                 self._task.args['_substitute_controller'] = True
             return self._format_rsync_rsh_target(host, path, user)


Test patch: