Repository: ansible/ansible

Issue:
Ansible 2.0 failing when remote shell is fish
This has been reported at https://groups.google.com/forum/#!topic/ansible-project/2JDkLFsvMcA and my example is this one:

```
ansible -i inventory crcweb -m ping -vvv
Using /home/jurgenhaas/.ansible.cfg as config file
<crcweb> ESTABLISH SSH CONNECTION FOR USER: jurgenhaas
<crcweb> SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=jurgenhaas -o ConnectTimeout=10 -o ControlPath=/home/jurgenhaas/.ansible/cp/ansible-ssh-%h-%p-%r -tt crcweb '( umask 22 && mkdir -p "$( echo $HOME/.ansible/tmp/ansible-tmp-1452763749.58-212919486518705 )" && echo "$( echo $HOME/.ansible/tmp/ansible-tmp-1452763749.58-212919486518705 )" )'
<crcweb> PUT /tmp/tmpEhWM_e TO       ^/ping
<crcweb> SSH: EXEC scp -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=jurgenhaas -o ConnectTimeout=10 -o ControlPath=/home/jurgenhaas/.ansible/cp/ansible-ssh-%h-%p-%r /tmp/tmpEhWM_e '[crcweb]:'"'"'      ^/ping'"'"''
crcweb | FAILED! => {
    "failed": true, 
    "msg": "ERROR! failed to transfer file to       ^/ping:\n\nscp:       ^/ping: No such file or directory\n"
}
```

When changing the remote shell, it is working just fine.



Discussion:
Is the shell type set to fish for these hosts?

http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters

The local shell is zsh, the remote term is sh.

The issue is that my local TERM environment (xterm-256color) was getting used by paramiko and openssh and sent to the remote system.

Temporary workaround, "TERM=vt100 ansible -i ... -m ping" makes everything work peachy.

One interesting thing to note is that enabling pipelining makes the problem go away. I was able to track down the issue in the paramiko code,
paramiko_ssh.py:219:            chan.get_pty(term=os.getenv('TERM', 'vt100'),

The local environment is getting used for TERM instead of always forcing vt100, I couldn't find the corresponding openssh code.

Hope this helps.

@fspadini these two issues are probably separate.  So I opened a new issue for yours: https://github.com/ansible/ansible/issues/13895

@abadger thanks for picking this up. However, I'm not sure what you're asking with your link above. The ansible_connection is ssh but I can't define fish as the shell there, do I?

Interesting though, when trying the suggestion from @fspadini to enable pipelining, then I'm getting this error:

```
crcweb | FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "Unsupported use of '='. To run 'LC_ALL=de_DE.UTF-8' with a modified environment, please use 'env LANG=de_DE.UTF-8 LC_ALL=de_DE.UTF-8…'\nfish: LANG=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LC_MESSAGES=de_DE.UTF-8 /usr/bin/python\n      ^\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "parsed": false
}
```

Sorry @fspadini I wasn't fully reading the content at your above link. I now saw that there is a new inventory variable ansible_shell_type which I can set to fish and that is working fine.

I just wonder what would be the best practice about this. Right now I included that in my inventory for everything, so all users and all hosts are expected to be using fish. But what if that wasn't the case? In the past, Ansible did not require that setting, right?

it did if you wanted to use the fish shell, but sometimes this 'worked' because /bin/sh was being forced, which also created some 'interesting' bugs.

you can set ansible_shell_type per host or group, so not all hosts need that.

@abadger cheers - sorry for mixing up the issues!

What do you do if different users have different shells? A group of admins who have their own accounts but have sudo access. Some have fish, some have bash. How do (if we can) we set ansible_shell_type  as an environment variable ?

That's a very good question and it could get even more complex. E.g. what if you're setting up a new host which originally doesn't have fish installed but as part of the playbook this will get installed. This will then probably fail half way through the play.

I still don't get it why this became a problem now and wasn't present before version 2.

so this might be an issue with the removal of the hardcoded /bin/sh that used to precede commands, in the attempt to make it more flexible and comply closer to what shells people had configured.

Is there way to force ansible to be consistent and use a particular shell for it's play (that is can we have it using /bin/sh)? Seems like this will produce different results based on what shell is configured for the current user. Consistency should be key.

Having the ability to specify a shell to use gives you other functionality like an audit. You could have a shell that logs everything and just wraps bash for example.

^ please test above PR, It should 'fix' most of these issues


Resolving patch:
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -481,8 +481,7 @@ def _execute_module(self, module_name=None, module_args=None, tmp=None, task_var
         display.debug("done with _execute_module (%s, %s)" % (module_name, module_args))
         return data
 
-    def _low_level_execute_command(self, cmd, sudoable=True, in_data=None,
-            executable=None, encoding_errors='replace'):
+    def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, executable=C.DEFAULT_EXECUTABLE, encoding_errors='replace'):
         '''
         This is the function which executes the low level shell command, which
         may be commands to create/remove directories for temporary files, or to
@@ -498,7 +497,7 @@ def _low_level_execute_command(self, cmd, sudoable=True, in_data=None,
         '''
 
         if executable is not None:
-            cmd = executable + ' -c ' + cmd
+            cmd = executable + ' -c ' + pipes.quote(cmd)
 
         display.debug("_low_level_execute_command(): starting")
         if not cmd:


Test patch:
diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py
--- a/test/units/plugins/action/test_action.py
+++ b/test/units/plugins/action/test_action.py
@@ -49,7 +49,7 @@ def test_sudo_only_if_user_differs(self):
 
         play_context.remote_user = 'apo'
         action_base._low_level_execute_command('ECHO', sudoable=True)
-        play_context.make_become_cmd.assert_called_once_with('ECHO', executable=None)
+        play_context.make_become_cmd.assert_called_once_with("/bin/sh -c ECHO", executable='/bin/sh')
 
         play_context.make_become_cmd.reset_mock()
 
@@ -58,6 +58,6 @@ def test_sudo_only_if_user_differs(self):
         try:
             play_context.remote_user = 'root'
             action_base._low_level_execute_command('ECHO SAME', sudoable=True)
-            play_context.make_become_cmd.assert_called_once_with('ECHO SAME', executable=None)
+            play_context.make_become_cmd.assert_called_once_with("/bin/sh -c 'ECHO SAME'", executable='/bin/sh')
         finally:
             C.BECOME_ALLOW_SAME_USER = become_allow_same_user