Repository: ansible/ansible

Issue:
Ansible SSH output gives: tcgetattr: Invalid argument
When using the raw module, and even in some cases with the command module we get output that starts with `tcgetattr: Invalid argument`.

I can reproduce this out put by doing something like: `ssh localhost ssh -tt -q localhost date` and this error is related to the pseudo-terminal we requested.

So I was wondering why we request a pseudo-terminal since this is not an interactive session.

If pseudo-terminals however are needed, it somehow seems that we request a pseudo-terminal even when stdin is not in fact a terminal: `ssh -tt -q localhost date < <(echo blah)`



Discussion:
It is required by most default-configured sudos out there. It won't read a password from anything not a tty.

Ok, I guess changing this behaviour in the cases when sudo is not being used, is not acceptable ?

So we have to understand exactly what is going on, because if I run this command from my shell there is no issue. If I modify `ssh -tt -q` into `ssh -T -q` it also works. And since I cannot reproduce it on my own laptop out of the box, it's hard to know what's going on :-/

It seems somewhere stdin is being stuck, which causes the pseudo-terminal to fail.

This fails on the affected systems:

```
- name: Test raw module
  hosts: all
  user: foo
  gather_facts: no

  tasks:
  - action: raw date
    delegate_to: bar
```

Then you get output like this:

```
PLAY [Test raw module] ********************
ok: [system1] => {"stdout": "tcgetattr: Invalid argument\nFri Nov 23 16:04:41 CEST 2012\r\n"}
ok: [system2] => {"stdout": "tcgetattr: Invalid argument\nFri Nov 23 16:04:41 CEST 2012\r\n"}
ok: [system3] => {"stdout": "tcgetattr: Invalid argument\nFri Nov 23 16:04:41 CEST 2012\r\n"}

PLAY RECAP *********************
system1                        : ok=1    changed=0    unreachable=0    failed=0
system2                        : ok=1    changed=0    unreachable=0    failed=0
system3                        : ok=1    changed=0    unreachable=0    failed=0
```

This happens not just when we target Solaris or AIX, but also Linux. But not on my own laptop, only on the server(s).

Parsing slash connection Code used to filter this message out, now the filtering code just removes trailing and leading non-JSON, I would suggest it consider filtering out tcgetattr messages again

-- Michael

On Nov 23, 2012, at 9:48 AM, Dag Wieers notifications@github.com wrote:

> When using the raw module, and even in some cases with the command module we get output that starts with tcgetattr: Invalid argument.
> 
> I can reproduce this out put by doing something like: ssh localhost ssh -tt -q localhost date and this error is related to the pseudo-terminal we requested.
> 
> So I was wondering why we request a pseudo-terminal since this is not an interactive session.
> 
> If pseudo-terminals however are needed, it somehow seems that we request a pseudo-terminal even when stdin is not in fact a terminal: ssh -tt -q localhost date < <(echo blah)
> 
> —
> Reply to this email directly or view it on GitHub.

I would prefer understanding the problem and seeing if we can avoid it somehow. Since I cannot reproduce it properly on my laptop, this is an issue...

I'm not sure any of us understand the problem :)

In any event, we've had this code in earlier versions, so going back
through connection history for paramiko should find what we need to add.

On Fri, Nov 23, 2012 at 10:31 AM, Dag Wieers notifications@github.comwrote:

> I would prefer understanding the problem and seeing if we can avoid it
> somehow. Since I cannot reproduce it properly on my laptop, this is an
> issue...
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/ansible/ansible/issues/1662#issuecomment-10662723.

We are using `ssh` connection type. We plan to move the command-and-control server to a newly deployed RHEL6 system. Let me first test if I can reproduce the problem on that system, because honestly I don't trust the current system designated for this. Can you keep this ticket open until then ? This is planned for early next week.

What does "don't trust the current system designated" mean?

Since I cannot reproduce it on my laptop, I suspect something to be different on the system (for whatever reason). But since I don't completely understand the problem it's hard to know where to look. (I already investigated terminal settings etc...)

So in the end it may not be Ansible, but maybe Ansible can avoid it nevertheless.

ok, yeah, the old (and we never understood it) tcgetattr filtering strategy
seemed to work pretty well.

it was basically "ignore any line starting with tcgetattr( up until the
newline", I think

it would be good if we understood it, but in practice it seemed that
various SSHes, particularly weird stuff like dropbear or old Ubuntu, just
spewed random messages and they were safe to ignore.

It is kind of the cost of doing business for the way Ansible manages
things, I think

On Fri, Nov 23, 2012 at 10:57 AM, Dag Wieers notifications@github.comwrote:

> Since I cannot reproduce it on my laptop, I suspect something to be
> different on the system (for whatever reason). But since I don't completely
> understand the problem it's hard to know where to look. (I already
> investigated terminal settings etc...)
> 
> So in the end it may not be Ansible, but maybe Ansible can avoid it
> nevertheless.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/ansible/ansible/issues/1662#issuecomment-10663366.


Resolving patch:
diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py
--- a/lib/ansible/runner/connection_plugins/ssh.py
+++ b/lib/ansible/runner/connection_plugins/ssh.py
@@ -99,8 +99,17 @@ def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False):
             ssh_cmd.append(cmd)
 
         vvv("EXEC %s" % ssh_cmd, host=self.host)
-        p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE,
-                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        try:
+            # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors
+            import pty
+            master, slave = pty.openpty()
+            p = subprocess.Popen(ssh_cmd, stdin=slave,
+                                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+            stdin = os.fdopen(master, 'w', 0)
+        except:
+            p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE,
+                                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+            stdin = p.stdin
 
         self._send_password()
 
@@ -118,7 +127,7 @@ def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False):
                 else:
                     stdout = p.communicate()
                     raise errors.AnsibleError('ssh connection error waiting for sudo password prompt')
-            p.stdin.write(self.runner.sudo_pass + '\n')
+            stdin.write(self.runner.sudo_pass + '\n')
             fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
 
         # We can't use p.communicate here because the ControlMaster may have stdout open as well
@@ -133,7 +142,7 @@ def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False):
                     break
             elif p.poll() is not None:
                 break
-        p.stdin.close()  # close stdin after we read from stdout (see also issue #848)
+        stdin.close() # close stdin after we read from stdout (see also issue #848)
 
         if p.returncode != 0 and stdout.find('Bad configuration option: ControlPersist') != -1:
             raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" (or ansible_ssh_args in the config file) before running again')


Test patch: