diff --git a/plugins/callback/report.py b/plugins/callback/report.py index f2a88fa..0bc5e5a 100644 --- a/plugins/callback/report.py +++ b/plugins/callback/report.py @@ -18,14 +18,14 @@ Example: example01.example.org - - [x] 10-028 - - [x] 10-032 - - [ ] 10-034 + - [x] 10-028: This task is ok + - [x] 10-032: This is ok as well + - [ ] 10-034: This task reported changed example02.example.org - - [x] 10-028 - - [ ] 10-032 - - [x] 10-034 + - [x] 10-028 A task that reported ok + - [~] 10-032 This task was skipped + - [x] 10-034 Another ok task options: {} # # Kept around as reference # format_string: @@ -41,11 +41,19 @@ import json from datetime import datetime +from enum import IntEnum from ansible.plugins.callback import CallbackBase from ansible.executor.task_result import TaskResult +class TaskState(IntEnum): + SKIPPED = 0 + OK = 1 + CHANGED = 2 + FAILED = 3 + + class CallbackModule(CallbackBase): """ Print Markdown checklists indicating which tasks require human interaction. @@ -61,49 +69,45 @@ def __init__(self): self.tasknames = {} super(CallbackModule, self).__init__() - def on_any(self, *args, **kwargs): - # on_any is a horribly hacky catch-all handler that's triggered on every single event during a play. - - # There is no clearly defined "event type" at the top level of the args/kwargs, so we carefully need to check - # the result object's internal structure. - if len(args) != 2 and len(args[0]) != 2: - return - task = args[0][0] - if not isinstance(task, TaskResult): + def _process_task_result(self, result, state): + if not isinstance(result, TaskResult): return - - if task.is_skipped(): - return - if task.is_failed(): - state = 2 - elif task.is_changed(): - state = 1 - else: - state = 0 - # Extract the task information from the TaskResult. # Unfortunately, some protected members need to be accessed to get the information we need. If something in # this plugin breaks with future ansible versions, it's probably the next 3 lines. - host = task._host.name - taskid = task._task.vars.get('taskid') - taskname = task._task.vars.get('name', '') + host = result._host.name + taskid = result._task.vars.get('taskid') + taskname = result._task.vars.get('name', '') if taskid is None or taskid == 'ignore-me': return - - # Store the "worst" result (max, failed=2, changed=1, ok=0) per host and taskid. + # Store the "worst" result (max, failed=3, changed=2, ok=1, skipped=0) per host and taskid. # E.g. if one subtask failed, consider the entire maintenance task failed. hostdict = self.hosts.setdefault(host, {}) - hostdict[taskid] = max(hostdict.get(taskid, 0), state) + hostdict[taskid] = max(hostdict.get(taskid, TaskState.SKIPPED), state) # Pipe `|` is used as the separator for "subtasks" if one maintenance task is split into multiple ansible tasks self.tasknames[taskid] = taskname.split('|', 1)[0].strip() + def v2_runner_on_failed(self, result, ignore_errors=False): + self._process_task_result(result, TaskState.FAILED) + + def v2_runner_on_skipped(self, result): + self._process_task_result(result, TaskState.SKIPPED) + + def v2_runner_on_ok(self, result): + if result.is_changed(): + self._process_task_result(result, TaskState.CHANGED) + else: + self._process_task_result(result, TaskState.OK) + def v2_playbook_on_stats(self, stats): # Generate checklist report at the end of the playbook run for host, tasks in self.hosts.items(): self._display.display('') self._display.display(host) for task, result in tasks.items(): - if result == 0: + if result == TaskState.SKIPPED: + self._display.display('- [~] %s: %s' % (task, self.tasknames.get(task, ''))) + elif result == TaskState.OK: self._display.display('- [x] %s: %s' % (task, self.tasknames.get(task, ''))) else: self._display.display('- [ ] %s: %s' % (task, self.tasknames.get(task, '')))