]>
Commit | Line | Data |
---|---|---|
f713a12c IB |
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) |