Skip to content

Commit

Permalink
WIP quick-n-dirty aggregate similar failures
Browse files Browse the repository at this point in the history
If a failure happened in multiple hosts, group them all into a single
summary item.
  • Loading branch information
rhcarvalho committed Jul 21, 2017
1 parent aebc18e commit bfc4186
Showing 1 changed file with 40 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Status: disabled permanently or until Ansible object has a public API.
# This does leave the code more likely to be broken by future Ansible changes.

from collections import defaultdict
from pprint import pformat

from ansible.plugins.callback import CallbackBase
Expand Down Expand Up @@ -57,6 +58,22 @@ def _print_failure_details(self, failures):
subsequent_indent = u' ' * initial_indent_len
subsequent_extra_indent = u' ' * (initial_indent_len + 10)

failures = [prepare_failure(failure['result']) for failure in failures]

d = defaultdict(list)
for failure in failures:
key = (failure['msg'], failure['play'], failure['task'])
d[key].append(failure)

r = []
for failure in failures:
key = (failure['msg'], failure['play'], failure['task'])
if not d[key]:
continue
failure['host'] = sorted(f['host'] for f in d.pop(key))
r.append(failure)
failures = r

for i, failure in enumerate(failures, 1):
entries = _format_failure(failure)
self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), entries[0]))
Expand All @@ -70,10 +87,10 @@ def _print_failure_details(self, failures):
# re: result attrs see top comment # pylint: disable=protected-access
for failure in failures:
# get context from check task result since callback plugins cannot access task vars
playbook_context = playbook_context or failure['result']._result.get('playbook_context')
playbook_context = failure['playbook_context']
failed_checks.update(
name
for name, result in failure['result']._result.get('checks', {}).items()
for name, result in failure['checks'].items()
if result.get('failed')
)
if failed_checks:
Expand Down Expand Up @@ -109,26 +126,33 @@ def _print_check_failure_summary(self, failed_checks, context):
self._display.display(summary)


def prepare_failure(failure):
return {
'host': failure._host.get_name(),
'play': play_name(failure._task),
'task': failure._task.get_name(),
'msg': failure._result.get('msg', u'none'),
'checks': failure._result.get('checks', {}),
'playbook_context': failure._result.get('playbook_context'),
}

# re: result attrs see top comment # pylint: disable=protected-access
def _format_failure(failure):
'''Return a list of pretty-formatted text entries describing a failure, including
relevant information about it. Expect that the list of text entries will be joined
by a newline separator when output to the user.'''
result = failure['result']
host = result._host.get_name()
play = _get_play(result._task)
if play:
play = play.get_name()
task = result._task.get_name()
msg = result._result.get('msg', u'???')
host = u', '.join(failure['host'])
play = failure['play']
task = failure['task']
msg = failure['msg']
fields = (
(u'Host', host),
(u'Hosts', host),
(u'Play', play),
(u'Task', task),
(u'Message', stringc(msg, C.COLOR_ERROR)),
)
if 'checks' in result._result:
fields += ((u'Details', _format_failed_checks(result._result['checks'])),)
if failure['checks']:
fields += ((u'Details', _format_failed_checks(failure['checks'])),)
row_format = '{:10}{}'
return [row_format.format(header + u':', body) for header, body in fields]

Expand All @@ -148,9 +172,10 @@ def _format_failed_checks(checks):

# This is inspired by ansible.playbook.base.Base.dump_me.
# re: play/task/block attrs see top comment # pylint: disable=protected-access
def _get_play(obj):
def play_name(obj):
'''Given a task or block, recursively tries to find its parent play.'''
if hasattr(obj, '_play'):
return obj._play
return obj._play.get_name()
if getattr(obj, '_parent'):
return _get_play(obj._parent)
return play_name(obj._parent)
return ''

0 comments on commit bfc4186

Please sign in to comment.