mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
better cleanup on task results display (#27175)
* better cleanup on task results display callbacks get 'clean' copy of result objects moved cleanup into result object itself removed now redundant callback cleanup moved no_log tests * moved import as per feedback
This commit is contained in:
parent
03abce2d39
commit
01b6c7c9c6
6 changed files with 64 additions and 19 deletions
|
@ -27,6 +27,7 @@ from ansible import constants as C
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError
|
||||||
from ansible.executor.play_iterator import PlayIterator
|
from ansible.executor.play_iterator import PlayIterator
|
||||||
from ansible.executor.stats import AggregateStats
|
from ansible.executor.stats import AggregateStats
|
||||||
|
from ansible.executor.task_result import TaskResult
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
from ansible.module_utils._text import to_text
|
from ansible.module_utils._text import to_text
|
||||||
from ansible.playbook.block import Block
|
from ansible.playbook.block import Block
|
||||||
|
@ -371,9 +372,20 @@ class TaskQueueManager:
|
||||||
if gotit is not None:
|
if gotit is not None:
|
||||||
methods.append(gotit)
|
methods.append(gotit)
|
||||||
|
|
||||||
|
# send clean copies
|
||||||
|
new_args = []
|
||||||
|
for arg in args:
|
||||||
|
# FIXME: add play/task cleaners
|
||||||
|
if isinstance(arg, TaskResult):
|
||||||
|
new_args.append(arg.clean_copy())
|
||||||
|
# elif isinstance(arg, Play):
|
||||||
|
# elif isinstance(arg, Task):
|
||||||
|
else:
|
||||||
|
new_args.append(arg)
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
try:
|
try:
|
||||||
method(*args, **kwargs)
|
method(*new_args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# TODO: add config toggle to make this fatal or not?
|
# TODO: add config toggle to make this fatal or not?
|
||||||
display.warning(u"Failure using method (%s) in callback plugin (%s): %s" % (to_text(method_name), to_text(callback_plugin), to_text(e)))
|
display.warning(u"Failure using method (%s) in callback plugin (%s): %s" % (to_text(method_name), to_text(callback_plugin), to_text(e)))
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from ansible.parsing.dataloader import DataLoader
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.vars.manager import strip_internal_keys
|
||||||
|
|
||||||
|
_IGNORE = ('changed', 'failed', 'skipped')
|
||||||
|
|
||||||
|
|
||||||
class TaskResult:
|
class TaskResult:
|
||||||
|
@ -69,3 +74,32 @@ class TaskResult:
|
||||||
if isinstance(res, dict):
|
if isinstance(res, dict):
|
||||||
flag |= res.get(key, False)
|
flag |= res.get(key, False)
|
||||||
return flag
|
return flag
|
||||||
|
|
||||||
|
def clean_copy(self):
|
||||||
|
|
||||||
|
''' returns 'clean' taskresult object '''
|
||||||
|
|
||||||
|
# FIXME: clean task_fields, _task and _host copies
|
||||||
|
result = TaskResult(self._host, self._task, {}, self._task_fields)
|
||||||
|
|
||||||
|
# statuses are already reflected on the event type
|
||||||
|
if result._task and result._task.action in ['debug']:
|
||||||
|
# debug is verbose by default to display vars, no need to add invocation
|
||||||
|
ignore = _IGNORE + ('invocation',)
|
||||||
|
else:
|
||||||
|
ignore = _IGNORE
|
||||||
|
|
||||||
|
if self._result.get('_ansible_no_log', False):
|
||||||
|
result._result = {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result"}
|
||||||
|
elif self._result:
|
||||||
|
result._result = deepcopy(self._result)
|
||||||
|
|
||||||
|
# actualy remove
|
||||||
|
for remove_key in ignore:
|
||||||
|
if remove_key in result._result:
|
||||||
|
del result._result[remove_key]
|
||||||
|
|
||||||
|
# remove almost ALL internal keys, keep ones relevant to callback
|
||||||
|
strip_internal_keys(result._result, exceptions=('_ansible_verbose_always', '_ansible_item_label', '_ansible_no_log'))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
|
@ -85,8 +85,6 @@ class CallbackBase(AnsiblePlugin):
|
||||||
self._plugin_options = options
|
self._plugin_options = options
|
||||||
|
|
||||||
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False):
|
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False):
|
||||||
if result.get('_ansible_no_log', False):
|
|
||||||
return json.dumps(dict(censored="the output has been hidden due to the fact that 'no_log: true' was specified for this result"))
|
|
||||||
|
|
||||||
if not indent and (result.get('_ansible_verbose_always') or self._display.verbosity > 2):
|
if not indent and (result.get('_ansible_verbose_always') or self._display.verbosity > 2):
|
||||||
indent = 4
|
indent = 4
|
||||||
|
@ -219,10 +217,8 @@ class CallbackBase(AnsiblePlugin):
|
||||||
del result._result['results']
|
del result._result['results']
|
||||||
|
|
||||||
def _clean_results(self, result, task_name):
|
def _clean_results(self, result, task_name):
|
||||||
if task_name in ['debug']:
|
''' removes data from results for display '''
|
||||||
for remove_key in ('changed', 'invocation', 'failed', 'skipped'):
|
pass
|
||||||
if remove_key in result:
|
|
||||||
del result[remove_key]
|
|
||||||
|
|
||||||
def set_play_context(self, play_context):
|
def set_play_context(self, play_context):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -72,14 +72,18 @@ def preprocess_vars(a):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def strip_internal_keys(dirty):
|
def strip_internal_keys(dirty, exceptions=None):
|
||||||
'''
|
'''
|
||||||
All keys stating with _ansible_ are internal, so create a copy of the 'dirty' dict
|
All keys stating with _ansible_ are internal, so create a copy of the 'dirty' dict
|
||||||
and remove them from the clean one before returning it
|
and remove them from the clean one before returning it
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if exceptions is None:
|
||||||
|
exceptions = ()
|
||||||
clean = dirty.copy()
|
clean = dirty.copy()
|
||||||
for k in dirty.keys():
|
for k in dirty.keys():
|
||||||
if isinstance(k, string_types) and k.startswith('_ansible_'):
|
if isinstance(k, string_types) and k.startswith('_ansible_'):
|
||||||
|
if k not in exceptions:
|
||||||
del clean[k]
|
del clean[k]
|
||||||
elif isinstance(dirty[k], dict):
|
elif isinstance(dirty[k], dict):
|
||||||
clean[k] = strip_internal_keys(dirty[k])
|
clean[k] = strip_internal_keys(dirty[k])
|
||||||
|
|
|
@ -138,3 +138,12 @@ class TestTaskResult(unittest.TestCase):
|
||||||
# test with failed_when in result
|
# test with failed_when in result
|
||||||
tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True))
|
tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True))
|
||||||
self.assertTrue(tr.is_failed())
|
self.assertTrue(tr.is_failed())
|
||||||
|
|
||||||
|
def test_task_result_no_log(self):
|
||||||
|
mock_host = MagicMock()
|
||||||
|
mock_task = MagicMock()
|
||||||
|
|
||||||
|
# no_log should remove secrets
|
||||||
|
tr = TaskResult(mock_host, mock_task, dict(_ansible_no_log=True, secret='DONTSHOWME'))
|
||||||
|
clean = tr.clean_copy()
|
||||||
|
self.assertTrue('secret' not in clean._result)
|
||||||
|
|
|
@ -93,16 +93,6 @@ class TestCallbackDumpResults(unittest.TestCase):
|
||||||
self.assertFalse('SENTINEL' in json_out)
|
self.assertFalse('SENTINEL' in json_out)
|
||||||
self.assertTrue('LEFTIN' in json_out)
|
self.assertTrue('LEFTIN' in json_out)
|
||||||
|
|
||||||
def test_no_log(self):
|
|
||||||
cb = CallbackBase()
|
|
||||||
result = {'item': 'some_item',
|
|
||||||
'_ansible_no_log': True,
|
|
||||||
'some_secrets': 'SENTINEL'}
|
|
||||||
json_out = cb._dump_results(result)
|
|
||||||
self.assertFalse('SENTINEL' in json_out)
|
|
||||||
self.assertTrue('no_log' in json_out)
|
|
||||||
self.assertTrue('output has been hidden' in json_out)
|
|
||||||
|
|
||||||
def test_exception(self):
|
def test_exception(self):
|
||||||
cb = CallbackBase()
|
cb = CallbackBase()
|
||||||
result = {'item': 'some_item LEFTIN',
|
result = {'item': 'some_item LEFTIN',
|
||||||
|
|
Loading…
Reference in a new issue