__metaclass__ = type import uuid from ansible import constants as C from ansible.playbook.task_include import TaskInclude from ansible.plugins.callback.default import CallbackModule as DefaultCallback from datetime import datetime class CallbackModule(DefaultCallback): def _output(self, msg, color=None): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") if isinstance(msg, list): output = ' | '.join([timestamp] + msg) else: output = timestamp + ' | ' + msg self._display.display(output, color=color) def _get_host(self, result): delegated_vars = result._result.get('_ansible_delegated_vars', None) if (getattr(result, '_host', False) and getattr(result._host, 'get_name', False)): msg = '%s' % result._host.get_name() elif (getattr(result, '_host', False) and getattr(result._host, 'name', False)): msg = '%s' % result._host.name else: msg = 'UNKNOWN' if delegated_vars: msg += ' -> %s' % delegated_vars['ansible_host'] return msg def _get_task_name(self, item=None): name = '' if item and getattr(item, 'name', False): # item is a task name = item.name elif item and getattr(item, 'task_name', False): name = item.task_name elif item and getattr(item, '_task', False): name = item._task.name return name def _get_uuid(self, item=None): uuid = '' if item and getattr(item, '_uuid', False): # item is a task uuid = item._uuid elif item and getattr(item, '_task', False): # item is a result (may not have a _task tho) if getattr(item._task, '_uuid', False): uuid = item._task._uuid return '{:36}'.format(uuid) def _get_state(self, state): return '{:>10}'.format(state) # TODO(mwhahaha): can this work for fatal/skipped/etc? def _get_item_line(self, result, item=False): line = [ self._get_uuid(result) ] host_str = self._get_host(result=result) if (getattr(result, '_result', False) and result._result.get('changed', False)): line.append(self._get_state('CHANGED')), line.append(self._get_task_name(result)) line.append(host_str) color = C.COLOR_CHANGED else: if not self.display_ok_hosts: return (None, None) line.append(self._get_state('OK')) line.append(self._get_task_name(result)) line.append(host_str) color = C.COLOR_OK if item: item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) return (line, color) def _handle_warnings(self, result): if not C.ACTION_WARNINGS: return if result.get('warnings', False): line = [ self._get_uuid(result), self._get_state('WARNING') ] color = C.COLOR_WARN for warn in result['warnings']: msg = line + [warn] self._output(msg, color) del result['warnings'] if result.get('deprecations', False): line = [ self._get_uuid(result), self._get_state('DEPRECATED') ] color = C.COLOR_DEPRECATE # TODO(mwhahaha): handle deps correctly as they are a dict for dep in result['deprecations']: msg = line + [dep['msg']] self._output(msg, color) del result['deprecations'] def _task_line(self, task, state, color=None): if not task.name: return line = [ self._get_uuid(task), self._get_state(state), self._get_task_name(task) ] self._output(line, color) def v2_playbook_on_task_start(self, task, is_conditional): self._task_line(task, 'TASK') def v2_playbook_on_handler_task_start(self, task): self._task_line(task, 'HANDLER') def v2_playbook_on_cleanup_task_start(self, task): self._task_line(task, 'CLEANUP') # TODO(mwhahaha): Push fix into default for broken version of this # function because get_option doesn't work when k is not in _plugin_options def v2_runner_on_start(self, host, task): if ('show_per_host_start' in self._plugin_options and self.get_options('show_per_host_start')): color = C.COLOR_HIGHLIGHT line = [ self._get_uuid(task), self._get_state('START'), self._get_task_name(task=task), host.name ] self._output(line, color) def v2_runner_item_on_ok(self, result): if isinstance(result._task, TaskInclude): return (line, color) = self._get_item_line(result, item=True) if not line: return self._handle_warnings(result._result) if result._task.loop and 'results' in result._result: self._process_items(result) else: if self._run_is_verbose(result): line.append('result=%s' % self._dump_results(result._result)) self._output(line, color) def v2_runner_on_failed(self, result, ignore_errors=False): self._clean_results(result._result, result._task.action) # TODO(mwhahaha): implement this one self._handle_exception(result._result) self._handle_warnings(result._result) if result._task.loop and 'results' in result._result: self._process_items(result) else: if ignore_errors: status = 'IGNORED' color = C.COLOR_SKIP else: status = 'FATAL' color = C.COLOR_ERROR line = [ self._get_uuid(result), self._get_state(status), self._get_task_name(result), self._get_host(result=result) ] item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) line.append('error=%s' % self._dump_results(result._result)) self._output(line, color) def v2_runner_item_on_failed(self, result): line = [ self._get_uuid(result), self._get_state('FATAL'), self._get_task_name(result), self._get_host(result=result) ] color = C.COLOR_ERROR item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) line.append('error=%s' % self._dump_results(result._result)) self._output(line, color) def v2_runner_on_ok(self, result): if isinstance(result._task, TaskInclude): return (line, color) = self._get_item_line(result) if not line: return self._handle_warnings(result._result) if result._task.loop and 'results' in result._result: self._process_items(result) else: if self._run_is_verbose(result): line.append('result=%s' % self._dump_results(result._result)) self._output(line, color) def v2_runner_item_on_skipped(self, result): if not C.DISPLAY_SKIPPED_HOSTS: return self._clean_results(result._result, result._task.action) line = [ self._get_uuid(result), self._get_state('SKIPPED'), self._get_task_name(result), self._get_host(result=result) ] color = C.COLOR_SKIP item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) if self._run_is_verbose(result): line.append('result=%s' % self._dump_results(result._result)) self._output(line, color) def v2_runner_on_skipped(self, result): # TODO(mwhahaha): this is broken? # if self.display_skipped_hosts: self._clean_results(result._result, result._task.action) if result._task.loop and 'results' in result._result: self._process_items(result) else: line = [ self._get_uuid(result), self._get_state('SKIPPED'), self._get_task_name(result), self._get_host(result=result) ] color = C.COLOR_SKIP item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) self._output(line, color) def v2_runner_on_unreachable(self, result): line = [ self._get_uuid(result), self._get_state('UNREACHABLE'), self._get_task_name(result), self._get_host(result=result) ] item_result = self._get_item_label(result._result) # don't display if None if item_result: line.append('item=%s' % item_result) self._output(line, C.COLOR_UNREACHABLE) def v2_playbook_on_include(self, included_file): color = C.COLOR_SKIP # included files don't have tasks so lets generate one for the file # for consistency. Should this be optional? file_id = str(uuid.uuid4()) for host in included_file._hosts: line = [ file_id, self._get_state('INCLUDED'), included_file._filename, host.name ] self._output(line, color) def v2_runner_retry(self, result): retry_count = result._result['retries'] - result._result['attempts'] # NOTE(mwhahaha): action is async_status we know we're waiting vs a # failure that is being retried. We can adjust state & color. # We use getattr because ansible will stop using this if we try and # access an undefined thing, so let's be careful. if (getattr(result, '_task', False) and (getattr(result._task, 'action', False) in ['async_status'])): state = 'WAITING' else: state = 'RETRY' color = C.COLOR_DEBUG host_str = self._get_host(result=result) line = [ self._get_uuid(result), self._get_state(state), self._get_task_name(result), host_str, '%d retries left' % retry_count ] if self._run_is_verbose(result, verbosity=2): line.append("result=%s" % self._dump_results(result._result)) self._output(line, color)