From 36e454c52f8a5f6fb3f45674c3ea9a5102ca2ade Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 27 Apr 2012 01:25:38 -0400 Subject: [PATCH] Because paramiko using a pty can't distinguish stderr and stdout, remove handling that treated -D as a way to show stderr, and make sure modules don't include things on stderr. Update docs and test module script to come into line. --- docs/man/man1/ansible-playbook.1.asciidoc.in | 3 +- docs/man/man1/ansible.1.asciidoc.in | 2 +- hacking/test-module | 5 +- lib/ansible/callbacks.py | 6 +-- lib/ansible/connection.py | 52 ++++++++++++-------- lib/ansible/runner.py | 10 ++-- lib/ansible/utils.py | 2 +- library/apt | 3 -- library/group | 9 ---- library/user | 9 ---- 10 files changed, 47 insertions(+), 54 deletions(-) diff --git a/docs/man/man1/ansible-playbook.1.asciidoc.in b/docs/man/man1/ansible-playbook.1.asciidoc.in index 2b2ab84d370..be6d97d69a2 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc.in +++ b/docs/man/man1/ansible-playbook.1.asciidoc.in @@ -36,8 +36,7 @@ OPTIONS *-D*, *--debug* -Print any messages the remote module sends to standard error to the console - +Debug mode *-i* 'PATH', *--inventory=*'PATH':: diff --git a/docs/man/man1/ansible.1.asciidoc.in b/docs/man/man1/ansible.1.asciidoc.in index eacb6015119..9c8ee4c847b 100644 --- a/docs/man/man1/ansible.1.asciidoc.in +++ b/docs/man/man1/ansible.1.asciidoc.in @@ -62,7 +62,7 @@ The 'ARGUMENTS' to pass to the module. *-D*, *--debug*:: -Print any messages the remote module sends to standard error to the console +Debug mode *-k*, *--ask-pass*:: diff --git a/hacking/test-module b/hacking/test-module index ce31b3bffcd..901839f8f33 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -63,7 +63,7 @@ cmd = subprocess.Popen("%s %s" % (modfile, argspath), if err and err != '': print "***********************************" - print "RECIEVED DATA ON STDOUT, WILL IGNORE THIS:" + print "RECIEVED DATA ON STDERR, THIS WILL BREAK YOUR MODULE:" print err try: @@ -84,6 +84,9 @@ print "PARSED OUTPUT" print utils.bigjson(results) +if err and err != '': + sys.exit(1) sys.exit(0) + diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index a3f46fc9d4a..62c506c8d3d 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -122,7 +122,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks): pass def on_error(self, host, err): - print >>sys.stderr, "stderr: [%s] => %s\n" % (host, err) + print >>sys.stderr, "err: [%s] => %s\n" % (host, err) def on_no_hosts(self): print >>sys.stderr, "no hosts matched\n" @@ -141,7 +141,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): self.stats = stats def on_unreachable(self, host, msg): - print "unreachable: [%s] => %s" % (host, msg) + print "fatal: [%s] => %s" % (host, msg) def on_failed(self, host, results): invocation = results.get('invocation',None) @@ -160,7 +160,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): print "ok: [%s] => %s\n" % (host, invocation) def on_error(self, host, err): - print >>sys.stderr, "stderr: [%s] => %s\n" % (host, err) + print >>sys.stderr, "err: [%s] => %s\n" % (host, err) def on_skipped(self, host): print "skipping: [%s]\n" % host diff --git a/lib/ansible/connection.py b/lib/ansible/connection.py index ac60ff3f3cc..02f4a44090c 100644 --- a/lib/ansible/connection.py +++ b/lib/ansible/connection.py @@ -39,9 +39,6 @@ with warnings.catch_warnings(): ################################################ -RANDOM_PROMPT_LEN = 32 # 32 random chars in [a-z] gives > 128 bits of entropy - - class Connection(object): ''' Handles abstract connections to remote hosts ''' @@ -96,12 +93,8 @@ class ParamikoConnection(object): except IOError,e: raise errors.AnsibleConnectionFailed(str(e)) - #if 'hostname' in credentials: - # self.host = credentials['hostname'] if 'port' in credentials: self.port = int(credentials['port']) - #if 'user' in credentials: - # user = credentials['user'] if 'identityfile' in credentials: keypair = os.path.expanduser(credentials['identityfile']) @@ -133,14 +126,22 @@ class ParamikoConnection(object): self.ssh = self._get_conn() return self - def exec_command(self, cmd, tmp_path, sudoable=False): # pylint: disable-msg=W0613 + def exec_command(self, cmd, tmp_path, sudoable=False): + ''' run a command on the remote host ''' - bufsize = 4096 # Could make this a Runner param if needed - timeout_secs = self.runner.timeout # Reusing runner's TCP connect timeout as command progress timeout + + bufsize = 4096 + # reusing runner's TCP connect timeout as command progress timeout (correct?) + timeout_secs = self.runner.timeout chan = self.ssh.get_transport().open_session() chan.settimeout(timeout_secs) - chan.get_pty() # Many sudo setups require a terminal; use in both cases for consistency - + stdin = chan.makefile('wb', bufsize) + stdout = chan.makefile('rb', bufsize) + stderr = chan.makefile_stderr('rb', bufsize) + chan.get_pty() + chan.set_combine_stderr(False) + + if not self.runner.sudo or not sudoable: quoted_command = '"$SHELL" -c ' + pipes.quote(cmd) chan.exec_command(quoted_command) @@ -151,17 +152,26 @@ class ParamikoConnection(object): # follows. Passing a quoted compound command to sudo (or sudo -s) # directly doesn't work, so we shellquote it with pipes.quote() # and pass the quoted string to the user's shell. - sudocmd = 'sudo -k -- "$SHELL" -c ' + pipes.quote(cmd) - chan.exec_command(sudocmd) - if self.runner.sudo_pass: - while not chan.recv_ready(): - time.sleep(0.25) - sudo_output = chan.recv(bufsize) # Pull prompt, catch errors, eat sudo output - chan.sendall(self.runner.sudo_pass + '\n') - stdin = chan.makefile('wb', bufsize) + randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) + prompt = '[sudo via ansible, key=%s] password: ' % randbits + sudocmd = 'sudo -k -p "%s" -- "$SHELL" -c %s' % (prompt, pipes.quote(cmd)) + sudo_output = '' + try: + chan.exec_command(sudocmd) + if self.runner.sudo_pass: + while not sudo_output.endswith(prompt): + chunk = chan.recv(bufsize) + if not chunk: + raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') + sudo_output += chunk + chan.sendall(self.runner.sudo_pass + '\n') + except socket.timeout: + raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output) + + stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('rb', bufsize) - stderr = chan.makefile_stderr('rb', bufsize) + stderr = chan.makefile_stderr('rb', bufsize) return stdin, stdout, stderr def put_file(self, in_path, out_path): diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 559465996f7..8e10d5b1df5 100644 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -637,16 +637,18 @@ class Runner(object): ''' execute a command string over SSH, return the output ''' stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudoable=sudoable) - + err=None + out=None if type(stderr) != str: err="\n".join(stderr.readlines()) else: err=stderr - if type(stdout) != str: - return "\n".join(stdout.readlines()), err + out="\n".join(stdout.readlines()) else: - return stdout, err + out=stdout + return (out,err) + # ***************************************************** diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 037afd871a4..cc6269895c1 100644 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -286,7 +286,7 @@ def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, asyn parser = SortedOptParser(usage) parser.add_option('-D','--debug', default=False, action="store_true", - help='debug standard error output of remote modules') + help='debug mode') parser.add_option('-f','--forks', dest='forks', default=constants.DEFAULT_FORKS, type='int', help="specify number of parallel processes to use (default=%s)" % constants.DEFAULT_FORKS) parser.add_option('-i', '--inventory-file', dest='inventory', diff --git a/library/apt b/library/apt index e3ebf0c6733..57e98e49f5d 100755 --- a/library/apt +++ b/library/apt @@ -30,9 +30,6 @@ import traceback APT_PATH = "/usr/bin/apt-get" APT = "DEBIAN_PRIORITY=critical %s" % APT_PATH -def debug(msg): - print >>sys.stderr, msg - def exit_json(rc=0, **kwargs): print json.dumps(kwargs) sys.exit(rc) diff --git a/library/group b/library/group index 99fa425988a..20984c4d771 100755 --- a/library/group +++ b/library/group @@ -31,14 +31,8 @@ GROUPADD = "/usr/sbin/groupadd" GROUPDEL = "/usr/sbin/groupdel" GROUPMOD = "/usr/sbin/groupmod" -def debug(msg): - # ansible ignores stderr, so it's safe to use for debug - print >>sys.stderr, msg - #pass - def exit_json(rc=0, **kwargs): if 'name' in kwargs: - debug("add group info to exit_json") add_group_info(kwargs) print json.dumps(kwargs) sys.exit(rc) @@ -59,7 +53,6 @@ def add_group_info(kwargs): def group_del(group): cmd = [GROUPDEL, group] - debug("Arguments to groupdel: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True @@ -73,7 +66,6 @@ def group_add(group, **kwargs): cmd.append('-g') cmd.append(kwargs[key]) cmd.append(group) - debug("Arguments to groupadd: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True @@ -91,7 +83,6 @@ def group_mod(group, **kwargs): if len(cmd) == 1: return False cmd.append(group) - debug("Arguments to groupmod: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True diff --git a/library/user b/library/user index e8e64520dcc..ba825e9b1ac 100755 --- a/library/user +++ b/library/user @@ -33,14 +33,8 @@ USERADD = "/usr/sbin/useradd" USERMOD = "/usr/sbin/usermod" USERDEL = "/usr/sbin/userdel" -def debug(msg): - # ansible ignores stderr, so it's safe to use for debug - print >>sys.stderr, msg - #pass - def exit_json(rc=0, **kwargs): if 'name' in kwargs: - debug("add user info to exit_json") add_user_info(kwargs) print json.dumps(kwargs) sys.exit(rc) @@ -75,7 +69,6 @@ def user_del(user, **kwargs): elif key == 'remove' and kwargs[key]: cmd.append('-r') cmd.append(user) - debug("Arguments to userdel: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True @@ -118,7 +111,6 @@ def user_add(user, **kwargs): else: cmd.append('-M') cmd.append(user) - debug("Arguments to useradd: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True @@ -172,7 +164,6 @@ def user_mod(user, **kwargs): if len(cmd) == 1: return False cmd.append(user) - debug("Arguments to usermod: %s" % (" ".join(cmd))) rc = subprocess.call(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if rc == 0: return True