diff --git a/bin/ansible b/bin/ansible index 61806cc9dc..cb61453ef7 100755 --- a/bin/ansible +++ b/bin/ansible @@ -27,7 +27,6 @@ from ansible import utils from ansible import errors from ansible import callbacks from ansible import inventory - ######################################################## class Cli(object): @@ -84,17 +83,17 @@ class Cli(object): inventory_manager.subset(options.subset) hosts = inventory_manager.list_hosts(pattern) if len(hosts) == 0: - print >>sys.stderr, "No hosts matched" + callbacks.display("No hosts matched", stderr=True) sys.exit(1) if options.listhosts: for host in hosts: - print ' %s' % host + callbacks.display(' %s' % host) sys.exit(0) if ((options.module_name == 'command' or options.module_name == 'shell') and not options.module_args): - print >>sys.stderr, "No argument passed to %s module" % options.module_name + callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True) sys.exit(1) sshpass = None @@ -124,7 +123,7 @@ class Cli(object): ) if options.seconds: - print "background launch...\n\n" + callbacks.display("background launch...\n\n", color='cyan') results, poller = runner.run_async(options.seconds) results = self.poll_while_needed(poller, options) else: @@ -147,6 +146,10 @@ class Cli(object): ######################################################## if __name__ == '__main__': + callbacks.display("", log_only=True) + callbacks.display(" ".join(sys.argv), log_only=True) + callbacks.display("", log_only=True) + cli = Cli() (options, args) = cli.parse() try: @@ -158,6 +161,6 @@ if __name__ == '__main__': sys.exit(2) except errors.AnsibleError, e: # Generic handler for ansible specific errors - print "ERROR: %s" % str(e) + callbacks.display("ERROR: %s" % str(e), stderr=True, color='red') sys.exit(1) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index a241b8e996..79c883fced 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -28,16 +28,17 @@ from ansible import errors from ansible import callbacks from ansible import utils from ansible.color import ANSIBLE_COLOR, stringc +from ansible.callbacks import display def colorize(lead, num, color): """ Print 'lead' = 'num' in 'color' """ - if num != 0 and ANSIBLE_COLOR: + if num != 0 and ANSIBLE_COLOR and color is not None: return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color)) else: return "%s=%-4s" % (lead, str(num)) -def hostcolor(host, stats): - if ANSIBLE_COLOR: +def hostcolor(host, stats, color=True): + if ANSIBLE_COLOR and color: if stats['failures'] != 0 or stats['unreachable'] != 0: return "%-37s" % stringc(host, 'red') elif stats['changed'] != 0: @@ -180,7 +181,7 @@ def main(args): pb.run() hosts = sorted(pb.stats.processed.keys()) - print callbacks.banner("PLAY RECAP") + display(callbacks.banner("PLAY RECAP")) playbook_cb.on_stats(pb.stats) for h in hosts: @@ -191,16 +192,28 @@ def main(args): if len(failed_hosts) > 0: filename = pb.generate_retry_inventory(failed_hosts) if filename: - print " to retry, use: --limit @%s\n" % filename + display(" to retry, use: --limit @%s\n" % filename) for h in hosts: t = pb.stats.summarize(h) - print "%s : %s %s %s %s" % ( + + display("%s : %s %s %s %s" % ( hostcolor(h, t), colorize('ok', t['ok'], 'green'), colorize('changed', t['changed'], 'yellow'), colorize('unreachable', t['unreachable'], 'red'), - colorize('failed', t['failures'], 'red')) + colorize('failed', t['failures'], 'red')), + screen_only=True + ) + + display("%s : %s %s %s %s" % ( + hostcolor(h, t, False), + colorize('ok', t['ok'], None), + colorize('changed', t['changed'], None), + colorize('unreachable', t['unreachable'], None), + colorize('failed', t['failures'], None)), + log_only=True + ) print "" @@ -208,16 +221,19 @@ def main(args): return 2 except errors.AnsibleError, e: - print >>sys.stderr, "ERROR: %s" % e + display("ERROR: %s" % e, color='red') return 1 return 0 if __name__ == "__main__": + display(" ", log_only=True) + display(" ".join(sys.argv), log_only=True) + display(" ", log_only=True) try: sys.exit(main(sys.argv[1:])) except errors.AnsibleError, e: - print >>sys.stderr, "ERROR: %s" % e + display("ERROR: %s" % e, color='red', stderr=True) sys.exit(1) diff --git a/examples/ansible.cfg b/examples/ansible.cfg index 6f472bf66b..4c7cbb6548 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -16,6 +16,12 @@ library = /usr/share/ansible module_name = command +# location for ansible log file. If set, will store output from ansible +# and ansible-playbook. If enabling, you may wish to configure +# logrotate. + +#log_path = /var/log/ansible.log + # home directory where temp files are stored on remote systems. Should # almost always contain $HOME or be a directory writeable by all users diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index 04dedb640d..a483517bb4 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -24,8 +24,13 @@ import random import fnmatch import tempfile import fcntl +import constants from ansible.color import stringc +import logging +if constants.DEFAULT_LOG_PATH != '': + logging.basicConfig(filename=constants.DEFAULT_LOG_PATH, level=logging.DEBUG, format='%(asctime)s %(message)s') + callback_plugins = [x for x in utils.plugins.callback_loader.all()] def get_cowsay_info(): @@ -83,16 +88,25 @@ def set_task(callback, task): for callback_plugin in callback_plugins: callback_plugin.task = task -def display(msg, color=None, stderr=False): +def display(msg, color=None, stderr=False, screen_only=False, log_only=False): # prevent a very rare case of interlaced multiprocess I/O log_flock() msg2 = msg if color: msg2 = stringc(msg, color) - if not stderr: - print msg2 - else: - print >>sys.stderr, msg2 + if not log_only: + if not stderr: + print msg2 + else: + print >>sys.stderr, msg2 + if constants.DEFAULT_LOG_PATH != '': + while msg.startswith("\n"): + msg = msg.replace("\n","") + if not screen_only: + if color == 'red': + logging.error(msg) + else: + logging.info(msg) log_unflock() def call_callback_module(method_name, *args, **kwargs): @@ -301,7 +315,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks): def on_unreachable(self, host, res): if type(res) == dict: res = res.get('msg','') - display("%s | FAILED => %s" % (host, res)) + display("%s | FAILED => %s" % (host, res), stderr=True, color='red') if self.options.tree: utils.write_tree_file( self.options.tree, host, @@ -334,7 +348,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks): super(CliRunnerCallbacks, self).on_async_ok(host, res, jid) def on_async_failed(self, host, res, jid): - display(" FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True))) + display(" FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)), color='red', stderr=True) super(CliRunnerCallbacks, self).on_async_failed(host,res,jid) def _on_any(self, host, result): @@ -475,8 +489,8 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): super(PlaybookRunnerCallbacks, self).on_async_ok(host, res, jid) def on_async_failed(self, host, res, jid): - msg = " FAILED on %s"%(jid, host) - display(msg, color='red') + msg = " FAILED on %s" % (jid, host) + display(msg, color='red', stderr=True) super(PlaybookRunnerCallbacks, self).on_async_failed(host,res,jid) def on_file_diff(self, host, diff): diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index fb6af67afc..486a63cdf0 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -101,13 +101,15 @@ DEFAULT_ACTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'acti DEFAULT_CALLBACK_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '/usr/share/ansible_plugins/callback_plugins')) DEFAULT_CONNECTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '/usr/share/ansible_plugins/connection_plugins')) DEFAULT_LOOKUP_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '/usr/share/ansible_plugins/lookup_plugins')) -DEFAULT_VARS_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '/usr/share/ansible_plugins/vars_plugins')) +DEFAULT_VARS_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '/usr/share/ansible_plugins/vars_plugins')) DEFAULT_FILTER_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '/usr/share/ansible_plugins/filter_plugins')) +DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', '')) + +ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) +ZEROMQ_PORT = int(get_config(p, 'fireball', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099)) # non-configurable things DEFAULT_SUDO_PASS = None DEFAULT_REMOTE_PASS = None DEFAULT_SUBSET = None -ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) -ZEROMQ_PORT = int(get_config(p, 'fireball', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099))