]> git.immae.eu Git - perso/Immae/Config/Ansible.git/blob - plugins/callback/concise.py
Make output less verbose
[perso/Immae/Config/Ansible.git] / plugins / callback / concise.py
1 # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2 # (c) 2017 Ansible Project
3 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5 from __future__ import (absolute_import, division, print_function)
6 __metaclass__ = type
7
8 DOCUMENTATION = '''
9 callback: default
10 type: stdout
11 short_description: default Ansible screen output
12 version_added: historical
13 description:
14 - This is the default output callback for ansible-playbook.
15 extends_documentation_fragment:
16 - default_callback
17 requirements:
18 - set as stdout in configuration
19 '''
20
21 from ansible import constants as C
22 from ansible.playbook.task_include import TaskInclude
23 from ansible.plugins.callback import CallbackBase
24 from ansible.utils.color import colorize, hostcolor
25
26
27 # These values use ansible.constants for historical reasons, mostly to allow
28 # unmodified derivative plugins to work. However, newer options added to the
29 # plugin are not also added to ansible.constants, so authors of derivative
30 # callback plugins will eventually need to add a reference to the common docs
31 # fragment for the 'default' callback plugin
32
33 # these are used to provide backwards compat with old plugins that subclass from default
34 # but still don't use the new config system and/or fail to document the options
35 COMPAT_OPTIONS = (('display_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS),
36 ('display_ok_hosts', True),
37 ('show_custom_stats', C.SHOW_CUSTOM_STATS),
38 ('display_failed_stderr', False),)
39
40
41 class CallbackModule(CallbackBase):
42
43 '''
44 This is the default callback interface, which simply prints messages
45 to stdout when new callback events are received.
46 '''
47
48 CALLBACK_VERSION = 2.0
49 CALLBACK_TYPE = 'stdout'
50 CALLBACK_NAME = 'concise'
51
52 def __init__(self):
53
54 self._play = None
55 self._last_task_banner = None
56 self._last_task_name = None
57 self._task_type_cache = {}
58 super(CallbackModule, self).__init__()
59
60 def set_options(self, task_keys=None, var_options=None, direct=None):
61
62 super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
63
64 # for backwards compat with plugins subclassing default, fallback to constants
65 for option, constant in COMPAT_OPTIONS:
66 try:
67 value = self.get_option(option)
68 except (AttributeError, KeyError):
69 value = constant
70 setattr(self, option, value)
71
72 def v2_runner_on_failed(self, result, ignore_errors=False):
73
74 delegated_vars = result._result.get('_ansible_delegated_vars', None)
75 self._clean_results(result._result, result._task.action)
76
77 if self._last_task_banner != result._task._uuid:
78 self._print_task_banner(result._task)
79
80 self._handle_exception(result._result, use_stderr=self.display_failed_stderr)
81 self._handle_warnings(result._result)
82
83 if result._task.loop and 'results' in result._result:
84 self._process_items(result)
85
86 else:
87 if delegated_vars:
88 self._display.display("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
89 self._dump_results(result._result)),
90 color=C.COLOR_ERROR, stderr=self.display_failed_stderr)
91 else:
92 self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)),
93 color=C.COLOR_ERROR, stderr=self.display_failed_stderr)
94
95 if ignore_errors:
96 self._display.display("...ignoring", color=C.COLOR_SKIP)
97
98 def v2_runner_on_ok(self, result):
99
100 delegated_vars = result._result.get('_ansible_delegated_vars', None)
101
102 if isinstance(result._task, TaskInclude):
103 return
104 elif result._result.get('changed', False):
105 if self._last_task_banner != result._task._uuid:
106 self._print_task_banner(result._task)
107
108 if delegated_vars:
109 msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
110 else:
111 msg = "changed: [%s]" % result._host.get_name()
112 color = C.COLOR_CHANGED
113 else:
114 if not self.display_ok_hosts:
115 return
116
117 if self._last_task_banner != result._task._uuid:
118 self._print_task_banner(result._task)
119
120 if delegated_vars:
121 msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
122 else:
123 msg = "ok: [%s]" % result._host.get_name()
124 color = C.COLOR_OK
125
126 self._handle_warnings(result._result)
127
128 if result._task.loop and 'results' in result._result:
129 self._process_items(result)
130 else:
131 self._clean_results(result._result, result._task.action)
132
133 if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
134 msg += " => %s" % (self._dump_results(result._result),)
135 self._display.display(msg, color=color)
136
137 def v2_runner_on_skipped(self, result):
138
139 if self.display_skipped_hosts:
140
141 self._clean_results(result._result, result._task.action)
142
143 if self._last_task_banner != result._task._uuid:
144 self._print_task_banner(result._task)
145
146 if result._task.loop and 'results' in result._result:
147 self._process_items(result)
148 else:
149 msg = "skipping: [%s]" % result._host.get_name()
150 if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
151 msg += " => %s" % self._dump_results(result._result)
152 self._display.display(msg, color=C.COLOR_SKIP)
153
154 def v2_runner_on_unreachable(self, result):
155 if self._last_task_banner != result._task._uuid:
156 self._print_task_banner(result._task)
157
158 delegated_vars = result._result.get('_ansible_delegated_vars', None)
159 if delegated_vars:
160 self._display.display("fatal: [%s -> %s]: UNREACHABLE! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
161 self._dump_results(result._result)),
162 color=C.COLOR_UNREACHABLE)
163 else:
164 self._display.display("fatal: [%s]: UNREACHABLE! => %s" % (result._host.get_name(), self._dump_results(result._result)), color=C.COLOR_UNREACHABLE)
165
166 def v2_playbook_on_no_hosts_matched(self):
167 self._display.display("skipping: no hosts matched", color=C.COLOR_SKIP)
168
169 def v2_playbook_on_no_hosts_remaining(self):
170 self._display.banner("NO MORE HOSTS LEFT")
171
172 def v2_playbook_on_task_start(self, task, is_conditional):
173 self._task_start(task, prefix='TASK')
174
175 def _task_start(self, task, prefix=None):
176 # Cache output prefix for task if provided
177 # This is needed to properly display 'RUNNING HANDLER' and similar
178 # when hiding skipped/ok task results
179 if prefix is not None:
180 self._task_type_cache[task._uuid] = prefix
181
182 # Preserve task name, as all vars may not be available for templating
183 # when we need it later
184 if self._play.strategy == 'free':
185 # Explicitly set to None for strategy 'free' to account for any cached
186 # task title from a previous non-free play
187 self._last_task_name = None
188 else:
189 self._last_task_name = task.get_name().strip()
190
191 # Display the task banner immediately if we're not doing any filtering based on task result
192 if self.display_skipped_hosts and self.display_ok_hosts:
193 self._print_task_banner(task)
194
195 def _print_task_banner(self, task):
196 # args can be specified as no_log in several places: in the task or in
197 # the argument spec. We can check whether the task is no_log but the
198 # argument spec can't be because that is only run on the target
199 # machine and we haven't run it thereyet at this time.
200 #
201 # So we give people a config option to affect display of the args so
202 # that they can secure this if they feel that their stdout is insecure
203 # (shoulder surfing, logging stdout straight to a file, etc).
204 args = ''
205 if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
206 args = u', '.join(u'%s=%s' % a for a in task.args.items())
207 args = u' %s' % args
208
209 prefix = self._task_type_cache.get(task._uuid, 'TASK')
210
211 # Use cached task name
212 task_name = self._last_task_name
213 if task_name is None:
214 task_name = task.get_name().strip()
215
216 self._display.banner(u"%s [%s%s]" % (prefix, task_name, args))
217 if self._display.verbosity >= 2:
218 path = task.get_path()
219 if path:
220 self._display.display(u"task path: %s" % path, color=C.COLOR_DEBUG)
221
222 self._last_task_banner = task._uuid
223
224 def v2_playbook_on_cleanup_task_start(self, task):
225 self._task_start(task, prefix='CLEANUP TASK')
226
227 def v2_playbook_on_handler_task_start(self, task):
228 self._task_start(task, prefix='RUNNING HANDLER')
229
230 def v2_playbook_on_play_start(self, play):
231 name = play.get_name().strip()
232 if not name:
233 msg = u"PLAY"
234 else:
235 msg = u"PLAY [%s]" % name
236
237 self._play = play
238
239 self._display.banner(msg)
240
241 def v2_on_file_diff(self, result):
242 if result._task.loop and 'results' in result._result:
243 printed_banner = False
244 for res in result._result['results']:
245 if 'diff' in res and res['diff'] and res.get('changed', False):
246 diff = self._get_diff(res['diff'])
247 if diff:
248 if not printed_banner and self._last_task_banner != result._task._uuid:
249 self._print_task_banner(result._task)
250 printed_banner = True
251 self._display.display(diff)
252 elif 'diff' in result._result and result._result['diff'] and result._result.get('changed', False):
253 diff = self._get_diff(result._result['diff'])
254 if diff:
255 if self._last_task_banner != result._task._uuid:
256 self._print_task_banner(result._task)
257 self._display.display(diff)
258
259 def v2_runner_item_on_ok(self, result):
260
261 delegated_vars = result._result.get('_ansible_delegated_vars', None)
262 self._clean_results(result._result, result._task.action)
263 if isinstance(result._task, TaskInclude):
264 return
265 elif result._result.get('changed', False):
266 if self._last_task_banner != result._task._uuid:
267 self._print_task_banner(result._task)
268
269 msg = 'changed'
270 color = C.COLOR_CHANGED
271 else:
272 if not self.display_ok_hosts:
273 return
274
275 if self._last_task_banner != result._task._uuid:
276 self._print_task_banner(result._task)
277
278 msg = 'ok'
279 color = C.COLOR_OK
280
281 if delegated_vars:
282 msg += ": [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
283 else:
284 msg += ": [%s]" % result._host.get_name()
285
286 msg += " => (item=%s)" % (self._get_item_label(result._result),)
287
288 if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
289 msg += " => %s" % self._dump_results(result._result)
290 self._display.display(msg, color=color)
291
292 def v2_runner_item_on_failed(self, result):
293 if self._last_task_banner != result._task._uuid:
294 self._print_task_banner(result._task)
295
296 delegated_vars = result._result.get('_ansible_delegated_vars', None)
297 self._clean_results(result._result, result._task.action)
298 self._handle_exception(result._result)
299
300 msg = "failed: "
301 if delegated_vars:
302 msg += "[%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
303 else:
304 msg += "[%s]" % (result._host.get_name())
305
306 self._handle_warnings(result._result)
307 self._display.display(msg + " (item=%s) => %s" % (self._get_item_label(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR)
308
309 def v2_runner_item_on_skipped(self, result):
310 if self.display_skipped_hosts:
311 if self._last_task_banner != result._task._uuid:
312 self._print_task_banner(result._task)
313
314 self._clean_results(result._result, result._task.action)
315 msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item_label(result._result))
316 if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
317 msg += " => %s" % self._dump_results(result._result)
318 self._display.display(msg, color=C.COLOR_SKIP)
319
320 def v2_playbook_on_include(self, included_file):
321 msg = 'included: %s for %s' % (included_file._filename, ", ".join([h.name for h in included_file._hosts]))
322 if 'item' in included_file._args:
323 msg += " => (item=%s)" % (self._get_item_label(included_file._args),)
324 self._display.display(msg, color=C.COLOR_SKIP)
325
326 def v2_playbook_on_stats(self, stats):
327 self._display.banner("PLAY RECAP")
328
329 hosts = sorted(stats.processed.keys())
330 for h in hosts:
331 t = stats.summarize(h)
332
333 self._display.display(u"%s : %s %s %s %s" % (
334 hostcolor(h, t),
335 colorize(u'ok', t['ok'], C.COLOR_OK),
336 colorize(u'changed', t['changed'], C.COLOR_CHANGED),
337 colorize(u'unreachable', t['unreachable'], C.COLOR_UNREACHABLE),
338 colorize(u'failed', t['failures'], C.COLOR_ERROR)),
339 screen_only=True
340 )
341
342 self._display.display(u"%s : %s %s %s %s" % (
343 hostcolor(h, t, False),
344 colorize(u'ok', t['ok'], None),
345 colorize(u'changed', t['changed'], None),
346 colorize(u'unreachable', t['unreachable'], None),
347 colorize(u'failed', t['failures'], None)),
348 log_only=True
349 )
350
351 self._display.display("", screen_only=True)
352
353 # print custom stats if required
354 if stats.custom and self.show_custom_stats:
355 self._display.banner("CUSTOM STATS: ")
356 # per host
357 # TODO: come up with 'pretty format'
358 for k in sorted(stats.custom.keys()):
359 if k == '_run':
360 continue
361 self._display.display('\t%s: %s' % (k, self._dump_results(stats.custom[k], indent=1).replace('\n', '')))
362
363 # print per run custom stats
364 if '_run' in stats.custom:
365 self._display.display("", screen_only=True)
366 self._display.display('\tRUN: %s' % self._dump_results(stats.custom['_run'], indent=1).replace('\n', ''))
367 self._display.display("", screen_only=True)
368
369 def v2_playbook_on_start(self, playbook):
370 if self._display.verbosity > 1:
371 from os.path import basename
372 self._display.banner("PLAYBOOK: %s" % basename(playbook._file_name))
373
374 if self._display.verbosity > 3:
375 # show CLI options
376 if self._options is not None:
377 for option in dir(self._options):
378 if option.startswith('_') or option in ['read_file', 'ensure_value', 'read_module']:
379 continue
380 val = getattr(self._options, option)
381 if val and self._display.verbosity > 3:
382 self._display.display('%s: %s' % (option, val), color=C.COLOR_VERBOSE, screen_only=True)
383
384 def v2_runner_retry(self, result):
385 task_name = result.task_name or result._task
386 msg = "FAILED - RETRYING: %s (%d retries left)." % (task_name, result._result['retries'] - result._result['attempts'])
387 if (self._display.verbosity > 2 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
388 msg += "Result was: %s" % self._dump_results(result._result)
389 self._display.display(msg, color=C.COLOR_DEBUG)
390
391 def v2_playbook_on_notify(self, handler, host):
392 if self._display.verbosity > 1:
393 self._display.display("NOTIFIED HANDLER %s for %s" % (handler.get_name(), host), color=C.COLOR_VERBOSE, screen_only=True)