diff options
Diffstat (limited to 'plugins/callback/concise.py')
-rw-r--r-- | plugins/callback/concise.py | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/plugins/callback/concise.py b/plugins/callback/concise.py new file mode 100644 index 0000000..4e6a39d --- /dev/null +++ b/plugins/callback/concise.py | |||
@@ -0,0 +1,393 @@ | |||
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) | ||