diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index b42c548ab20..821c6a5f81b 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -440,7 +440,7 @@ class Runner(object): host_variables = self.inventory.get_variables(host) host_connection = host_variables.get('ansible_connection', self.transport) - if host_connection in [ 'paramiko', 'paramiko_alt', 'ssh', 'ssh_alt', 'accelerate' ]: + if host_connection in [ 'paramiko', 'paramiko_old', 'ssh', 'ssh_old', 'accelerate' ]: port = host_variables.get('ansible_ssh_port', self.remote_port) if port is None: port = C.DEFAULT_REMOTE_PORT @@ -627,7 +627,7 @@ class Runner(object): if not self.accelerate_port: self.accelerate_port = C.ACCELERATE_PORT - if actual_transport in [ 'paramiko', 'paramiko_alt', 'ssh', 'ssh_alt', 'accelerate' ]: + if actual_transport in [ 'paramiko', 'paramiko_old', 'ssh', 'ssh_old', 'accelerate' ]: actual_port = inject.get('ansible_ssh_port', port) # the delegated host may have different SSH port configured, etc @@ -892,7 +892,7 @@ class Runner(object): if result['rc'] != 0: if result['rc'] == 5: output = 'Authentication failure.' - elif result['rc'] == 255 and self.transport in ['ssh', 'ssh_alt']: + elif result['rc'] == 255 and self.transport in ['ssh', 'ssh_old']: if utils.VERBOSITY > 3: output = 'SSH encountered an unknown error. The output was:\n%s' % (result['stdout']+result['stderr']) else: diff --git a/lib/ansible/runner/connection_plugins/paramiko_alt.py b/lib/ansible/runner/connection_plugins/paramiko_old.py similarity index 96% rename from lib/ansible/runner/connection_plugins/paramiko_alt.py rename to lib/ansible/runner/connection_plugins/paramiko_old.py index fbba732631c..667e03c0e88 100644 --- a/lib/ansible/runner/connection_plugins/paramiko_alt.py +++ b/lib/ansible/runner/connection_plugins/paramiko_old.py @@ -121,7 +121,7 @@ class Connection(object): self.user = user self.password = password self.private_key_file = private_key_file - self.has_pipelining = True + self.has_pipelining = False def _cache_key(self): return "%s__%s__" % (self.host, self.user) @@ -179,6 +179,9 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' + if in_data: + raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") + bufsize = 4096 try: chan = self.ssh.get_transport().open_session() @@ -188,12 +191,12 @@ class Connection(object): msg += ": %s" % str(e) raise errors.AnsibleConnectionFailed(msg) - if not self.runner.sudo or not sudoable or in_data: + if not self.runner.sudo or not sudoable: if executable: quoted_command = executable + ' -c ' + pipes.quote(cmd) else: quoted_command = cmd - vvv("EXEC ALT no-tty %s" % quoted_command, host=self.host) + vvv("EXEC %s" % quoted_command, host=self.host) chan.exec_command(quoted_command) else: # sudo usually requires a PTY (cf. requiretty option), therefore @@ -224,17 +227,8 @@ class Connection(object): except socket.timeout: raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output) - if in_data: - try: - stdin = chan.makefile('wb') - stdin.write(in_data) - chan.shutdown_write() - except Exception, e: - raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh.') - stdout = ''.join(chan.makefile('rb', bufsize)) stderr = ''.join(chan.makefile_stderr('rb', bufsize)) - return (chan.recv_exit_status(), '', stdout, stderr) def put_file(self, in_path, out_path): diff --git a/lib/ansible/runner/connection_plugins/paramiko_ssh.py b/lib/ansible/runner/connection_plugins/paramiko_ssh.py index 667e03c0e88..fbba732631c 100644 --- a/lib/ansible/runner/connection_plugins/paramiko_ssh.py +++ b/lib/ansible/runner/connection_plugins/paramiko_ssh.py @@ -121,7 +121,7 @@ class Connection(object): self.user = user self.password = password self.private_key_file = private_key_file - self.has_pipelining = False + self.has_pipelining = True def _cache_key(self): return "%s__%s__" % (self.host, self.user) @@ -179,9 +179,6 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' - if in_data: - raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") - bufsize = 4096 try: chan = self.ssh.get_transport().open_session() @@ -191,12 +188,12 @@ class Connection(object): msg += ": %s" % str(e) raise errors.AnsibleConnectionFailed(msg) - if not self.runner.sudo or not sudoable: + if not self.runner.sudo or not sudoable or in_data: if executable: quoted_command = executable + ' -c ' + pipes.quote(cmd) else: quoted_command = cmd - vvv("EXEC %s" % quoted_command, host=self.host) + vvv("EXEC ALT no-tty %s" % quoted_command, host=self.host) chan.exec_command(quoted_command) else: # sudo usually requires a PTY (cf. requiretty option), therefore @@ -227,8 +224,17 @@ class Connection(object): except socket.timeout: raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output) + if in_data: + try: + stdin = chan.makefile('wb') + stdin.write(in_data) + chan.shutdown_write() + except Exception, e: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh.') + stdout = ''.join(chan.makefile('rb', bufsize)) stderr = ''.join(chan.makefile_stderr('rb', bufsize)) + return (chan.recv_exit_status(), '', stdout, stderr) def put_file(self, in_path, out_path): diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py index ea0857d4061..d987d609a88 100644 --- a/lib/ansible/runner/connection_plugins/ssh.py +++ b/lib/ansible/runner/connection_plugins/ssh.py @@ -45,7 +45,7 @@ class Connection(object): self.password = password self.private_key_file = private_key_file self.HASHED_KEY_MAGIC = "|1|" - self.has_pipelining = False + self.has_pipelining = True fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700) @@ -148,11 +148,13 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' - if in_data: - raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") - ssh_cmd = self._password_cmd() - ssh_cmd += ["ssh", "-tt"] + ssh_cmd += ["ssh", "-C"] + if not in_data: + # we can only use tty when we are not pipelining the modules. piping data into /usr/bin/python + # inside a tty automatically invokes the python interactive-mode but the modules are not + # compatible with the interactive-mode ("unexpected indent" mainly because of empty lines) + ssh_cmd += ["-tt"] if utils.VERBOSITY > 3: ssh_cmd += ["-vvv"] else: @@ -182,45 +184,81 @@ class Connection(object): fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) - - - try: - # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors - master, slave = pty.openpty() - p = subprocess.Popen(ssh_cmd, stdin=slave, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = os.fdopen(master, 'w', 0) - os.close(slave) - except: + # create process + if in_data: + # do not use pseudo-pty p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdin = p.stdin + else: + # try to use upseudo-pty + try: + # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors + master, slave = pty.openpty() + p = subprocess.Popen(ssh_cmd, stdin=slave, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = os.fdopen(master, 'w', 0) + os.close(slave) + except: + p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = p.stdin self._send_password() if self.runner.sudo and sudoable and self.runner.sudo_pass: + # several cases are handled for sudo privileges with password + # * NOPASSWD (tty & no-tty): detect success_key on stdout + # * without NOPASSWD: + # * detect prompt on stdout (tty) + # * detect prompt on stderr (no-tty) fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) + fcntl.fcntl(p.stderr, fcntl.F_SETFL, + fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK) sudo_output = '' + sudo_errput = '' + while not sudo_output.endswith(prompt) and success_key not in sudo_output: - rfd, wfd, efd = select.select([p.stdout], [], + rfd, wfd, efd = select.select([p.stdout, p.stderr], [], [p.stdout], self.runner.timeout) + if p.stderr in rfd: + chunk = p.stderr.read() + if not chunk: + raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') + sudo_errput += chunk + incorrect_password = gettext.dgettext( + "sudo", "Sorry, try again.") + if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)): + raise errors.AnsibleError('Incorrect sudo password') + elif sudo_errput.endswith(prompt): + stdin.write(self.runner.sudo_pass + '\n') + if p.stdout in rfd: chunk = p.stdout.read() if not chunk: raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') sudo_output += chunk - else: + + if not rfd: + # timeout. wrap up process communication stdout = p.communicate() raise errors.AnsibleError('ssh connection error waiting for sudo password prompt') + if success_key not in sudo_output: stdin.write(self.runner.sudo_pass + '\n') fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) - + fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK) # We can't use p.communicate here because the ControlMaster may have stdout open as well stdout = '' stderr = '' rpipes = [p.stdout, p.stderr] + if in_data: + try: + stdin.write(in_data) + stdin.close() + except: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') while True: rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) @@ -255,14 +293,16 @@ class Connection(object): # the host to known hosts is not intermingled with multiprocess output. fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN) fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) + controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 if C.HOST_KEY_CHECKING: if ssh_cmd[0] == "sshpass" and p.returncode == 6: raise errors.AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host\'s fingerprint to your known_hosts file to manage this host.') - controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 if p.returncode != 0 and controlpersisterror: 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') + if p.returncode == 255 and in_data: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') return (p.returncode, '', stdout, stderr) diff --git a/lib/ansible/runner/connection_plugins/ssh_alt.py b/lib/ansible/runner/connection_plugins/ssh_old.py similarity index 80% rename from lib/ansible/runner/connection_plugins/ssh_alt.py rename to lib/ansible/runner/connection_plugins/ssh_old.py index d987d609a88..ea0857d4061 100644 --- a/lib/ansible/runner/connection_plugins/ssh_alt.py +++ b/lib/ansible/runner/connection_plugins/ssh_old.py @@ -45,7 +45,7 @@ class Connection(object): self.password = password self.private_key_file = private_key_file self.HASHED_KEY_MAGIC = "|1|" - self.has_pipelining = True + self.has_pipelining = False fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700) @@ -148,13 +148,11 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' + if in_data: + raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") + ssh_cmd = self._password_cmd() - ssh_cmd += ["ssh", "-C"] - if not in_data: - # we can only use tty when we are not pipelining the modules. piping data into /usr/bin/python - # inside a tty automatically invokes the python interactive-mode but the modules are not - # compatible with the interactive-mode ("unexpected indent" mainly because of empty lines) - ssh_cmd += ["-tt"] + ssh_cmd += ["ssh", "-tt"] if utils.VERBOSITY > 3: ssh_cmd += ["-vvv"] else: @@ -184,81 +182,45 @@ class Connection(object): fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) - # create process - if in_data: - # do not use pseudo-pty + + + try: + # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors + master, slave = pty.openpty() + p = subprocess.Popen(ssh_cmd, stdin=slave, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = os.fdopen(master, 'w', 0) + os.close(slave) + except: p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdin = p.stdin - else: - # try to use upseudo-pty - try: - # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors - master, slave = pty.openpty() - p = subprocess.Popen(ssh_cmd, stdin=slave, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = os.fdopen(master, 'w', 0) - os.close(slave) - except: - p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = p.stdin self._send_password() if self.runner.sudo and sudoable and self.runner.sudo_pass: - # several cases are handled for sudo privileges with password - # * NOPASSWD (tty & no-tty): detect success_key on stdout - # * without NOPASSWD: - # * detect prompt on stdout (tty) - # * detect prompt on stderr (no-tty) fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) - fcntl.fcntl(p.stderr, fcntl.F_SETFL, - fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK) sudo_output = '' - sudo_errput = '' - while not sudo_output.endswith(prompt) and success_key not in sudo_output: - rfd, wfd, efd = select.select([p.stdout, p.stderr], [], + rfd, wfd, efd = select.select([p.stdout], [], [p.stdout], self.runner.timeout) - if p.stderr in rfd: - chunk = p.stderr.read() - if not chunk: - raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') - sudo_errput += chunk - incorrect_password = gettext.dgettext( - "sudo", "Sorry, try again.") - if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)): - raise errors.AnsibleError('Incorrect sudo password') - elif sudo_errput.endswith(prompt): - stdin.write(self.runner.sudo_pass + '\n') - if p.stdout in rfd: chunk = p.stdout.read() if not chunk: raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') sudo_output += chunk - - if not rfd: - # timeout. wrap up process communication + else: stdout = p.communicate() raise errors.AnsibleError('ssh connection error waiting for sudo password prompt') - if success_key not in sudo_output: stdin.write(self.runner.sudo_pass + '\n') fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) - fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK) + # We can't use p.communicate here because the ControlMaster may have stdout open as well stdout = '' stderr = '' rpipes = [p.stdout, p.stderr] - if in_data: - try: - stdin.write(in_data) - stdin.close() - except: - raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') while True: rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) @@ -293,16 +255,14 @@ class Connection(object): # the host to known hosts is not intermingled with multiprocess output. fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN) fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) - controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 if C.HOST_KEY_CHECKING: if ssh_cmd[0] == "sshpass" and p.returncode == 6: raise errors.AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host\'s fingerprint to your known_hosts file to manage this host.') + controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 if p.returncode != 0 and controlpersisterror: 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') - if p.returncode == 255 and in_data: - raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') return (p.returncode, '', stdout, stderr)