|
|
|
|
@ -27,6 +27,11 @@ DOCUMENTATION = '''
|
|
|
|
|
- key: show_custom_stats
|
|
|
|
|
section: defaults
|
|
|
|
|
type: bool
|
|
|
|
|
notes:
|
|
|
|
|
- When using a strategy such as free, host_pinned, or a custom strategy, host results will
|
|
|
|
|
be added to new task results in ``.plays[].tasks[]``. As such, there will exist duplicate
|
|
|
|
|
task objects indicated by duplicate task IDs at ``.plays[].tasks[].task.id``, each with an
|
|
|
|
|
individual host result for the task.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
|
@ -35,10 +40,14 @@ import json
|
|
|
|
|
from functools import partial
|
|
|
|
|
|
|
|
|
|
from ansible.inventory.host import Host
|
|
|
|
|
from ansible.module_utils._text import to_text
|
|
|
|
|
from ansible.parsing.ajson import AnsibleJSONEncoder
|
|
|
|
|
from ansible.plugins.callback import CallbackBase
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LOCKSTEP_CALLBACKS = frozenset(('linear', 'debug'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def current_time():
|
|
|
|
|
return '%sZ' % datetime.datetime.utcnow().isoformat()
|
|
|
|
|
|
|
|
|
|
@ -51,12 +60,15 @@ class CallbackModule(CallbackBase):
|
|
|
|
|
def __init__(self, display=None):
|
|
|
|
|
super(CallbackModule, self).__init__(display)
|
|
|
|
|
self.results = []
|
|
|
|
|
self._task_map = {}
|
|
|
|
|
self._is_lockstep = False
|
|
|
|
|
|
|
|
|
|
def _new_play(self, play):
|
|
|
|
|
self._is_lockstep = play.strategy in LOCKSTEP_CALLBACKS
|
|
|
|
|
return {
|
|
|
|
|
'play': {
|
|
|
|
|
'name': play.get_name(),
|
|
|
|
|
'id': str(play._uuid),
|
|
|
|
|
'id': to_text(play._uuid),
|
|
|
|
|
'duration': {
|
|
|
|
|
'start': current_time()
|
|
|
|
|
}
|
|
|
|
|
@ -68,7 +80,7 @@ class CallbackModule(CallbackBase):
|
|
|
|
|
return {
|
|
|
|
|
'task': {
|
|
|
|
|
'name': task.get_name(),
|
|
|
|
|
'id': str(task._uuid),
|
|
|
|
|
'id': to_text(task._uuid),
|
|
|
|
|
'duration': {
|
|
|
|
|
'start': current_time()
|
|
|
|
|
}
|
|
|
|
|
@ -76,13 +88,32 @@ class CallbackModule(CallbackBase):
|
|
|
|
|
'hosts': {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _find_result_task(self, host, task):
|
|
|
|
|
key = (host.get_name(), task._uuid)
|
|
|
|
|
return self._task_map.get(
|
|
|
|
|
key,
|
|
|
|
|
self.results[-1]['tasks'][-1]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def v2_playbook_on_play_start(self, play):
|
|
|
|
|
self.results.append(self._new_play(play))
|
|
|
|
|
|
|
|
|
|
def v2_runner_on_start(self, host, task):
|
|
|
|
|
if self._is_lockstep:
|
|
|
|
|
return
|
|
|
|
|
key = (host.get_name(), task._uuid)
|
|
|
|
|
task_result = self._new_task(task)
|
|
|
|
|
self._task_map[key] = task_result
|
|
|
|
|
self.results[-1]['tasks'].append(task_result)
|
|
|
|
|
|
|
|
|
|
def v2_playbook_on_task_start(self, task, is_conditional):
|
|
|
|
|
if not self._is_lockstep:
|
|
|
|
|
return
|
|
|
|
|
self.results[-1]['tasks'].append(self._new_task(task))
|
|
|
|
|
|
|
|
|
|
def v2_playbook_on_handler_task_start(self, task):
|
|
|
|
|
if not self._is_lockstep:
|
|
|
|
|
return
|
|
|
|
|
self.results[-1]['tasks'].append(self._new_task(task))
|
|
|
|
|
|
|
|
|
|
def _convert_host_to_name(self, key):
|
|
|
|
|
@ -120,14 +151,22 @@ class CallbackModule(CallbackBase):
|
|
|
|
|
"""This function is used as a partial to add failed/skipped info in a single method"""
|
|
|
|
|
host = result._host
|
|
|
|
|
task = result._task
|
|
|
|
|
task_result = result._result.copy()
|
|
|
|
|
task_result.update(on_info)
|
|
|
|
|
task_result['action'] = task.action
|
|
|
|
|
self.results[-1]['tasks'][-1]['hosts'][host.name] = task_result
|
|
|
|
|
|
|
|
|
|
result_copy = result._result.copy()
|
|
|
|
|
result_copy.update(on_info)
|
|
|
|
|
result_copy['action'] = task.action
|
|
|
|
|
|
|
|
|
|
task_result = self._find_result_task(host, task)
|
|
|
|
|
|
|
|
|
|
task_result['hosts'][host.name] = result_copy
|
|
|
|
|
end_time = current_time()
|
|
|
|
|
self.results[-1]['tasks'][-1]['task']['duration']['end'] = end_time
|
|
|
|
|
task_result['task']['duration']['end'] = end_time
|
|
|
|
|
self.results[-1]['play']['duration']['end'] = end_time
|
|
|
|
|
|
|
|
|
|
if not self._is_lockstep:
|
|
|
|
|
key = (host.get_name(), task._uuid)
|
|
|
|
|
del self._task_map[key]
|
|
|
|
|
|
|
|
|
|
def __getattribute__(self, name):
|
|
|
|
|
"""Return ``_record_task_result`` partial with a dict containing skipped/failed if necessary"""
|
|
|
|
|
if name not in ('v2_runner_on_ok', 'v2_runner_on_failed', 'v2_runner_on_unreachable', 'v2_runner_on_skipped'):
|
|
|
|
|
|