Repository: ansible/ansible

Issue:
raw module not working for Cisco Routers
<!--- 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

<!--- Paste verbatim output from “ansible --version” between quotes below -->

```
ansible 2.1.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

None relating to SSH.
##### 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.
-->

A fresh virtual env.
##### SUMMARY

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

The `raw` Module fails to connect via SSH under unknown circumstances. Executing the debug messages of Ansible directly works though. It works on routers with more modern SSH versions.
##### STEPS TO REPRODUCE

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

Execute command with the `raw` Module, even consider the `executable` advice from  #16054:

<!--- Paste example playbooks or commands between quotes below -->

```
naegele@librenms0:~$ ansible all -i some-router.company.net, -m raw -a 'executable="" show int desc' -vvv
No config file found; using defaults
<some-router.company.net> ESTABLISH SSH CONNECTION FOR USER: None
<some-router.company.net> SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/naegele/.ansible/cp/ansible-ssh-%h-%p-%r -tt some-router.company.net 'show int desc'
some-router.company.net | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh.",
    "unreachable": true
}
```

Copying the command from above into the shell directly:

```
naegele@librenms0:~$ ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/neuner/.ansible/cp/ansible-ssh-%h-%p-%r -tt some-router.company.net 'show int desc'


Mon Jun  6 12:55:17.543 CEST

Interface          Status      Protocol    Description
--------------------------------------------------------------------------------
Lo0                up          up          ***
<snip>
Te0/1/0/2          admin-down  admin-down
Te0/1/0/3          up          up          ***
```

<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS

<!--- What did you expect to happen when running the steps above? -->

It should work in either case.
##### ACTUAL RESULTS

<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

It only works when working around ansible by pasting the command directly. However, both attemps look the same in the routers log:

<!--- Paste verbatim command output between quotes below -->

```
RP/0/RSP0/CPU0:some-router#RP/0/RSP0/CPU0:Jun  6 13:43:30 CEST:
SSHD_[65888]: %SECURITY-SSHD-4-WARNING_LOGIN : The requested term-type
'screen' is not supported
RP/0/RSP0/CPU0:Jun  6 13:43:31 CEST: SSHD_[65888]:
%SECURITY-SSHD-6-INFO_SUCCESS_COMMAND : Successfully authenticated user
'naegele' from '2001::ffff' on 'vty1'(cipher 'aes128-cbc', mac
'hmac-md5', command 'show int desc')



RP/0/RSP0/CPU0:some-router#RP/0/RSP0/CPU0:Jun  6 13:43:40 CEST:
SSHD_[65888]: %SECURITY-SSHD-4-WARNING_LOGIN : The requested term-type
'screen' is not supported
RP/0/RSP0/CPU0:Jun  6 13:43:40 CEST: SSHD_[65888]:
%SECURITY-SSHD-6-INFO_SUCCESS_COMMAND : Successfully authenticated user
'naegele' from '2001::ffff' on 'vty1'(cipher 'aes128-cbc', mac
'hmac-md5', command 'show int desc')
```

Setting `TERM=vt100` clears the warning from the Cisco's log, but does not help otherwise.

Again, note that it works on a router with a more recent version of sshd.



Discussion:

Resolving patch:
diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py
--- a/lib/ansible/playbook/play_context.py
+++ b/lib/ansible/playbook/play_context.py
@@ -29,11 +29,9 @@
 from ansible.compat.six import iteritems, string_types
 from ansible import constants as C
 from ansible.errors import AnsibleError
-from ansible.playbook.attribute import Attribute, FieldAttribute
+from ansible.playbook.attribute import FieldAttribute
 from ansible.playbook.base import Base
-from ansible.template import Templar
 from ansible.utils.boolean import boolean
-from ansible.utils.unicode import to_unicode
 
 __all__ = ['PlayContext']
 
@@ -447,16 +445,21 @@ def make_become_cmd(self, cmd, executable=None):
         success_key = None
         self.prompt = None
 
-        if executable is None:
-            executable = self.executable
-
         if self.become:
 
+            if executable is None:
+                executable = self.executable
+
             becomecmd   = None
             randbits    = ''.join(random.choice(string.ascii_lowercase) for x in range(32))
             success_key = 'BECOME-SUCCESS-%s' % randbits
             success_cmd = pipes.quote('echo %s; %s' % (success_key, cmd))
 
+            if executable:
+                command = '%s -c %s' % (executable, success_cmd)
+            else:
+                command = success_cmd
+
             # set executable to use for the privilege escalation method, with various overrides
             exe = self.become_exe or \
                   getattr(self, '%s_exe' % self.become_method, None) or \
@@ -485,9 +488,9 @@ def make_become_cmd(self, cmd, executable=None):
                 # force quick error if password is required but not supplied, should prevent sudo hangs.
                 if self.become_pass:
                     prompt = '[sudo via ansible, key=%s] password: ' % randbits
-                    becomecmd = '%s %s -p "%s" -u %s %s -c %s' % (exe,  flags.replace('-n',''), prompt, self.become_user, executable, success_cmd)
+                    becomecmd = '%s %s -p "%s" -u %s %s' % (exe,  flags.replace('-n',''), prompt, self.become_user, command)
                 else:
-                    becomecmd = '%s %s -u %s %s -c %s' % (exe, flags, self.become_user, executable, success_cmd)
+                    becomecmd = '%s %s -u %s %s' % (exe, flags, self.become_user, command)
 
 
             elif self.become_method == 'su':
@@ -498,7 +501,7 @@ def detect_su_prompt(data):
                     return bool(SU_PROMPT_LOCALIZATIONS_RE.match(data))
                 prompt = detect_su_prompt
 
-                becomecmd = '%s %s %s -c %s' % (exe, flags, self.become_user, pipes.quote('%s -c %s' % (executable, success_cmd)))
+                becomecmd = '%s %s %s -c %s' % (exe, flags, self.become_user, pipes.quote(command))
 
             elif self.become_method == 'pbrun':
 
@@ -534,7 +537,7 @@ def detect_su_prompt(data):
 
                 exe = self.become_exe or 'dzdo'
 
-                becomecmd = '%s -u %s %s -c %s' % (exe, self.become_user, executable, success_cmd)
+                becomecmd = '%s -u %s %s' % (exe, self.become_user, command)
 
             else:
                 raise AnsibleError("Privilege escalation method not found: %s" % self.become_method)
diff --git a/lib/ansible/plugins/action/raw.py b/lib/ansible/plugins/action/raw.py
--- a/lib/ansible/plugins/action/raw.py
+++ b/lib/ansible/plugins/action/raw.py
@@ -37,7 +37,7 @@ def run(self, tmp=None, task_vars=None):
             result['skipped'] = True
             return result
 
-        executable = self._task.args.get('executable')
+        executable = self._task.args.get('executable', False)
         result.update(self._low_level_execute_command(self._task.args.get('_raw_params'), executable=executable))
 
         return result
diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py
--- a/lib/ansible/plugins/action/script.py
+++ b/lib/ansible/plugins/action/script.py
@@ -19,7 +19,6 @@
 
 import os
 
-from ansible import constants as C
 from ansible.plugins.action import ActionBase
 
 
@@ -79,7 +78,6 @@ def run(self, tmp=None, task_vars=None):
         tmp_src = self._connection._shell.join_path(tmp, os.path.basename(source))
         self._transfer_file(source, tmp_src)
 
-        sudoable = True
         # set file permissions, more permissive when the copy is done as a different user
         self._fixup_perms(tmp, remote_user, execute=True, recursive=True)
 


Test patch:
diff --git a/test/units/plugins/action/test_raw.py b/test/units/plugins/action/test_raw.py
--- a/test/units/plugins/action/test_raw.py
+++ b/test/units/plugins/action/test_raw.py
@@ -54,7 +54,7 @@ def test_raw_executable_is_not_empty_string(self):
         self.mock_am.display = Mock()
 
         self.mock_am.run()
-        self.mock_am._low_level_execute_command.assert_called_with('Args1', executable=None)
+        self.mock_am._low_level_execute_command.assert_called_with('Args1', executable=False)
 
     def test_raw_check_mode_is_True(self):