diff --git a/bin/ansible-playbook b/bin/ansible-playbook index a4071d5e57..5e76432495 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -33,6 +33,8 @@ def main(args): # create parser for CLI options parser = OptionParser() parser.usage = "ans-playbook playbook.yml ..." + parser.add_option('-D','--debug', default=False, action="store_true", + help='enable standard error debugging of modules.') parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int', help='set the number of forks to start up') parser.add_option("-i", "--inventory-file", dest="inventory", @@ -76,6 +78,7 @@ def main(args): extra_vars=options.extra_vars, module_path=options.module_path, forks=options.forks, + debug=options.debug, verbose=True, remote_pass=sshpass, remote_port=options.remote_port, diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index e20f6cee73..7cc4200811 100755 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -19,6 +19,7 @@ ####################################################### import utils +import sys ####################################################### @@ -146,6 +147,9 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): else: print "ok: [%s] => %s\n" % (host, invocation) + def on_error(self, host, err): + print >>sys.stderr, "stderr: [%s] => %s\n" % (host, err) + def on_skipped(self, host): print "skipping: [%s]\n" % host diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 4dd5906498..5af26f4242 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -57,6 +57,7 @@ class PlayBook(object): remote_port = C.DEFAULT_REMOTE_PORT, override_hosts = None, extra_vars = None, + debug = False, verbose = False, callbacks = None, runner_callbacks = None, @@ -72,6 +73,7 @@ class PlayBook(object): self.remote_user = remote_user self.remote_pass = remote_pass self.remote_port = remote_port + self.debug = debug self.verbose = verbose self.callbacks = callbacks self.runner_callbacks = runner_callbacks @@ -270,7 +272,7 @@ class PlayBook(object): remote_port=self.remote_port, setup_cache=SETUP_CACHE, basedir=self.basedir, conditional=only_if, callbacks=self.runner_callbacks, - extra_vars=self.extra_vars, sudo=sudo + extra_vars=self.extra_vars, debug=self.debug, sudo=sudo ) if async_seconds == 0: diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index ef8b72423a..3d83640fb4 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -74,7 +74,7 @@ class Runner(object): remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None, transport='paramiko', conditional='True', groups={}, callbacks=None, verbose=False, - sudo=False, extra_vars=None): + debug=False, sudo=False, extra_vars=None): if setup_cache is None: setup_cache = {} @@ -103,6 +103,7 @@ class Runner(object): self.module_args = module_args self.extra_vars = extra_vars self.timeout = timeout + self.debug = debug self.verbose = verbose self.remote_user = remote_user self.remote_pass = remote_pass @@ -227,16 +228,18 @@ class Runner(object): # ***************************************************** - def _return_from_module(self, conn, host, result, executed=None): + def _return_from_module(self, conn, host, result, err, executed=None): ''' helper function to handle JSON parsing of results ''' try: result = utils.parse_json(result) if executed is not None: result['invocation'] = executed - return [ host, True, result ] + if 'stderr' in result: + err="%s%s"%(err,result['stderr']) + return [host, True, result, err] except Exception, e: - return [ host, False, "%s/%s/%s" % (str(e), result, executed) ] + return [host, False, "%s/%s/%s" % (str(e), result, executed), err] # ***************************************************** @@ -337,7 +340,7 @@ class Runner(object): inject = self.setup_cache.get(conn.host,{}) conditional = utils.double_template(self.conditional, inject) if not eval(conditional): - return [ utils.smjson(dict(skipped=True)), 'skipped' ] + return [ utils.smjson(dict(skipped=True)), None, 'skipped' ] if Runner._external_variable_script is not None: self._add_variables_from_script(conn, inject) @@ -354,7 +357,8 @@ class Runner(object): cmd = "%s %s" % (remote_module_path, argsfile) else: cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]]) - return [ self._exec_command(conn, cmd, tmp, sudoable=True), client_executed_str ] + res, err = self._exec_command(conn, cmd, tmp, sudoable=True) + return ( res, err, client_executed_str ) # ***************************************************** @@ -387,12 +391,12 @@ class Runner(object): self.module_args += " #USE_SHELL" module = self._transfer_module(conn, tmp, module_name) - (result, executed) = self._execute_module(conn, tmp, module, self.module_args) + (result, err, executed) = self._execute_module(conn, tmp, module, self.module_args) if module_name == 'setup': self._add_result_to_setup_cache(conn, result) - return self._return_from_module(conn, host, result, executed) + return self._return_from_module(conn, host, result, err, executed) # ***************************************************** @@ -408,13 +412,13 @@ class Runner(object): async = self._transfer_module(conn, tmp, 'async_wrapper') module = self._transfer_module(conn, tmp, module_name) - (result, executed) = self._execute_module(conn, tmp, async, module_args, + (result, err, executed) = self._execute_module(conn, tmp, async, module_args, async_module=module, async_jid=self.generated_jid, async_limit=self.background ) - return self._return_from_module(conn, host, result, executed) + return self._return_from_module(conn, host, result, err, executed) # ***************************************************** @@ -436,30 +440,30 @@ class Runner(object): # run the copy module args = "src=%s dest=%s" % (tmp_src, dest) - (result1, executed) = self._execute_module(conn, tmp, module, args) - (host, ok, data) = self._return_from_module(conn, host, result1, executed) + (result1, err, executed) = self._execute_module(conn, tmp, module, args) + (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) if ok: - return self._chain_file_module(conn, tmp, data, options, executed) + return self._chain_file_module(conn, tmp, data, err, options, executed) else: - return (host, ok, data) + return (host, ok, data, err) # ***************************************************** - def _chain_file_module(self, conn, tmp, data, options, executed): + def _chain_file_module(self, conn, tmp, data, err, options, executed): ''' handles changing file attribs after copy/template operations ''' old_changed = data.get('changed', False) module = self._transfer_module(conn, tmp, 'file') args = ' '.join([ "%s=%s" % (k,v) for (k,v) in options.items() ]) - (result2, executed2) = self._execute_module(conn, tmp, module, args) - results2 = self._return_from_module(conn, conn.host, result2, executed) - (host, ok, data2) = results2 + (result2, err2, executed2) = self._execute_module(conn, tmp, module, args) + results2 = self._return_from_module(conn, conn.host, result2, err2, executed) + (host, ok, data2, err2) = results2 new_changed = data2.get('changed', False) data.update(data2) if old_changed or new_changed: data['changed'] = True - return (host, ok, data) + return (host, ok, data, "%s%s"%(err,err2)) # ***************************************************** @@ -487,19 +491,19 @@ class Runner(object): # run the template module args = "src=%s dest=%s metadata=%s" % (temppath, dest, metadata) - (result1, executed) = self._execute_module(conn, tmp, template_module, args) - (host, ok, data) = self._return_from_module(conn, host, result1, executed) + (result1, err, executed) = self._execute_module(conn, tmp, template_module, args) + (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) if ok: - return self._chain_file_module(conn, tmp, data, options, executed) + return self._chain_file_module(conn, tmp, data, err, options, executed) else: - return (host, ok, data) + return (host, ok, data, err) # ***************************************************** def _executor(self, host): try: - (host, ok, data) = self._executor_internal(host) + (host, ok, data, err) = self._executor_internal(host) if not ok: self.callbacks.on_unreachable(host, data) return (host, ok, data) @@ -517,7 +521,7 @@ class Runner(object): ok, conn = self._connect(host) if not ok: - return [ host, False, conn ] + return [ host, False, conn , None] cache = self.setup_cache.get(host, {}) module_name = utils.template(self.module_name, cache) @@ -538,7 +542,7 @@ class Runner(object): self._delete_remote_files(conn, tmp) conn.close() - (host, connect_ok, data) = result + (host, connect_ok, data, err) = result if not connect_ok: self.callbacks.on_unreachable(host, data) else: @@ -549,6 +553,9 @@ class Runner(object): else: self.callbacks.on_ok(host, data) + if self.debug and err: + self.callbacks.on_error(host, err) + return result # ***************************************************** @@ -561,17 +568,23 @@ class Runner(object): conn.exec_command('/usr/bin/logger -t ansible -p auth.info "%s"' % msg, None) # now run actual command stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudoable=sudoable) - if type(stdout) != str: - return "\n".join(stdout.readlines()) + + if type(stderr) != str: + err="\n".join(stderr.readlines()) else: - return stdout + err=stderr + + if type(stdout) != str: + return "\n".join(stdout.readlines()), err + else: + return stdout, err # ***************************************************** def _get_tmp_path(self, conn): ''' gets a temporary path on a remote box ''' - result = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX", None, sudoable=False) + result, err = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX", None, sudoable=False) cleaned = result.split("\n")[0].strip() + '/' return cleaned