Repository: ansible/ansible

Issue:
synchronize module breaks when ansible_shell_type=fish
Using ansible@devel https://github.com/ansible/ansible/commit/04d74fd6804b5a851cc8762cecf07b100e4dcc6f, using an account with Fish as its shell, I can successfully use the synchronize module:

```
~/s/config (master|✚26…) ❯❯❯ 
ansible -i hosts -m synchronize -a "src=/home/scott/.config/fish/ dest=/home/scott/.config/fish delete=yes" localhost -vvvv
No config file found; using defaults
Loaded callback minimal of type stdout, v2.0
ESTABLISH LOCAL CONNECTION FOR USER: scott
127.0.0.1 EXEC ( umask 22 && mkdir -p "$( echo $HOME/.ansible/tmp/ansible-tmp-1449685345.99-147311751194700 )" && echo "$( echo $HOME/.ansible/tmp/ansible-tmp-1449685345.99-147311751194700 )" )
127.0.0.1 PUT /tmp/tmporuqsm TO /home/scott/.ansible/tmp/ansible-tmp-1449685345.99-147311751194700/synchronize
127.0.0.1 EXEC LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /home/scott/.pyenv/versions/2.7.10/bin/python2.7 /home/scott/.ansible/tmp/ansible-tmp-1449685345.99-147311751194700/synchronize; rm -rf "/home/scott/.ansible/tmp/ansible-tmp-1449685345.99-147311751194700/" > /dev/null 2>&1
localhost | SUCCESS => {
    "changed": false, 
    "cmd": "rsync --delay-updates -F --compress --delete-after --archive --rsh 'ssh  -S none -o StrictHostKeyChecking=no' --out-format='<<CHANGED>>%i %n%L' \"/home/scott/.config/fish/\" \"/home/scott/.config/fish\"", 
    "invocation": {
        "module_args": {
            "_local_rsync_path": "rsync", 
            "delete": "yes", 
            "dest": "/home/scott/.config/fish", 
            "src": "/home/scott/.config/fish/"
        }, 
        "module_name": "synchronize"
    }, 
    "msg": "", 
    "rc": 0, 
    "stdout_lines": []
}
```

But if ansible_shell_type=fish is set, it breaks:

```
~/s/config (master|✚26…) ❯❯❯ 
ansible -i hosts -m synchronize -a "src=/home/scott/.config/fish/ dest=/home/scott/.config/fish delete=yes" -e "ansible_shell_type=fish" localhost -vvvv
No config file found; using defaults
Loaded callback minimal of type stdout, v2.0
ESTABLISH LOCAL CONNECTION FOR USER: scott
127.0.0.1 EXEC  umask 22 ; and mkdir -p ( echo $HOME/.ansible/tmp/ansible-tmp-1449685279.64-250696661210290 ) ; and echo ( echo $HOME/.ansible/tmp/ansible-tmp-1449685279.64-250696661210290 ) 
localhost | UNREACHABLE! => {
    "changed": false, 
    "msg": "ERROR! Authentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in \"/tmp\". Failed command was:  umask 22 ; and mkdir -p ( echo $HOME/.ansible/tmp/ansible-tmp-1449685279.64-250696661210290 ) ; and echo ( echo $HOME/.ansible/tmp/ansible-tmp-1449685279.64-250696661210290 ) , exited with result 1", 
    "unreachable": true
}
```

I tried setting remote_temp=/tmp in ~/.ansible.cfg as the error message suggested, but that made no difference.



Discussion:
`ansible_shell_type=fish` is for when you have fish as the shell on the target machine, it does not matter on the 'controller' except for when you `delegate_to: localhost` or use `local_action`

Understood. I used these snippets to illustrate the bug I've encountered , the practical effect is that when I'm using the synchronize module to rsync between the controller and the target, if I've set ansible_shell_type=fish on the target, I get this error. I can work around it by setting ansible_shell_type=csh on the task or by using the shell module and writing the rsync command line myself, but neither seems ideal.

In my illustration I'm rsyncing to and from the same machine, a machine which uses Fish as the shell, so both the controller and the target are running Fish as the shell.

I have a similar issue, when using `local_action` when using the git module. (I'm also using fish shell running on both remote and local machine).

a change that might have fixed this was merged to devel here: https://github.com/ansible/ansible/pull/14006  and into stable-2.0 here: https://github.com/ansible/ansible/commit/8647fdc7f25ba1c707c5333ca1581a1fa0ffe566

Could someone check whether that fixed this issue?  If so the fix will be in the 2.0.1 release.

I installed 8647fdc7f25ba1c707c5333ca1581a1fa0ffe566 with:

```
~/s/config (master|✚8…) ❯❯❯ pip2 uninstall ansible
~/s/config (master|✚8…) ❯❯❯ pip2 install git+https://github.com/ansible/ansible.git@8647fdc7f25ba1c707c5333ca1581a1fa0ffe566
```

and ran the same command and got the same error:

```
~/s/config (master|✚8…) ❯❯❯ 
ansible -i hosts -m synchronize -a "src=/home/scott/.config/fish/ dest=/home/scott/.config/fish delete=yes" -e "ansible_shell_type=fish" localhost -vvvv
No config file found; using defaults
Loaded callback minimal of type stdout, v2.0
ESTABLISH LOCAL CONNECTION FOR USER: scott
127.0.0.1 EXEC /bin/sh -c ' umask 22 ; and mkdir -p ( echo $HOME/.ansible/tmp/ansible-tmp-1453842977.55-256037622019004 ) ; and echo ( echo $HOME/.ansible/tmp/ansible-tmp-1453842977.55-256037622019004 ) '
localhost | UNREACHABLE! => {
    "changed": false, 
    "msg": "Authentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in \"/tmp\". Failed command was:  umask 22 ; and mkdir -p ( echo $HOME/.ansible/tmp/ansible-tmp-1453842977.55-256037622019004 ) ; and echo ( echo $HOME/.ansible/tmp/ansible-tmp-1453842977.55-256037622019004 ) , exited with result 1", 
    "unreachable": true
}
```

Could it be that you need to specify `executable` to `/usr/bin/fish` in `ansible.cfg`. The problem is that `and` and `or` are used and that doesn't work with `/bin/sh`.


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
@@ -20,6 +20,7 @@
 
 import os.path
 
+from ansible.playbook.play_context import MAGIC_VARIABLE_MAPPING
 from ansible.plugins.action import ActionBase
 from ansible.plugins import connection_loader
 from ansible.utils.boolean import boolean
@@ -185,7 +186,7 @@ def run(self, tmp=None, task_vars=None):
         localhost_ports = set()
         for host in C.LOCALHOST:
             localhost_vars = task_vars['hostvars'].get(host, {})
-            for port_var in ('ansible_port', 'ansible_ssh_port'):
+            for port_var in MAGIC_VARIABLE_MAPPING['port']:
                 port = localhost_vars.get(port_var, None)
                 if port:
                     break
@@ -228,6 +229,21 @@ def run(self, tmp=None, task_vars=None):
         if not use_delegate and remote_transport:
             # Create a connection to localhost to run rsync on
             new_stdin = self._connection._new_stdin
+
+            # Unike port, there can be only one shell
+            localhost_shell = None
+            for host in C.LOCALHOST:
+                localhost_vars = task_vars['hostvars'].get(host, {})
+                for shell_var in MAGIC_VARIABLE_MAPPING['shell']:
+                    localhost_shell = localhost_vars.get(shell_var, None)
+                    if localhost_shell:
+                        break
+                if localhost_shell:
+                    break
+            else:
+                localhost_shell = os.path.basename(C.DEFAULT_EXECUTABLE)
+            self._play_context.shell = localhost_shell
+
             new_connection = connection_loader.get('local', self._play_context, new_stdin)
             self._connection = new_connection
             self._override_module_replaced_vars(task_vars)


Test patch: