diff --git a/changelogs/fragments/callback_plugin_merge.yml b/changelogs/fragments/callback_plugin_merge.yml new file mode 100644 index 0000000000..19281415fb --- /dev/null +++ b/changelogs/fragments/callback_plugin_merge.yml @@ -0,0 +1,10 @@ +--- +deprecated_features: + - The `skippy`, `full_skip`, `actionable`, and `stderr` callback plugins have + been deprecated in favor of config options that influence the behavior of the + `default` callback plugin (https://github.com/ansible/ansible/pull/41058) +minor_changes: + - New config options `display_ok_hosts` and `display_failed_stderr` (along with + the existing `display_skipped_hosts` option) allow more fine-grained control + over the way that ansible displays output from a playbook + (https://github.com/ansible/ansible/pull/41058) diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py index e56bf8b702..6cd89305e3 100644 --- a/lib/ansible/plugins/callback/__init__.py +++ b/lib/ansible/plugins/callback/__init__.py @@ -134,7 +134,7 @@ class CallbackBase(AnsiblePlugin): self._display.deprecated(**warning) del res['deprecations'] - def _handle_exception(self, result): + def _handle_exception(self, result, use_stderr=False): if 'exception' in result: msg = "An exception occurred during task execution. " @@ -146,7 +146,7 @@ class CallbackBase(AnsiblePlugin): msg = "The full traceback is:\n" + result['exception'] del result['exception'] - self._display.display(msg, color=C.COLOR_ERROR) + self._display.display(msg, color=C.COLOR_ERROR, stderr=use_stderr) def _get_diff(self, difflist): diff --git a/lib/ansible/plugins/callback/actionable.py b/lib/ansible/plugins/callback/actionable.py index 6d6df3cc56..f7a565f03b 100644 --- a/lib/ansible/plugins/callback/actionable.py +++ b/lib/ansible/plugins/callback/actionable.py @@ -14,6 +14,10 @@ DOCUMENTATION = ''' - Use this callback when you dont care about OK nor Skipped. - This callback suppresses any non Failed or Changed status. version_added: "2.1" + deprecated: + why: The 'default' callback plugin now supports this functionality + removed_in: '2.11' + alternative: "'default' callback plugin with 'display_skipped_hosts = no' and 'display_ok_hosts = no' options" extends_documentation_fragment: - default_callback requirements: diff --git a/lib/ansible/plugins/callback/default.py b/lib/ansible/plugins/callback/default.py index 763039e9ea..dbbed043bd 100644 --- a/lib/ansible/plugins/callback/default.py +++ b/lib/ansible/plugins/callback/default.py @@ -39,6 +39,7 @@ class CallbackModule(CallbackBase): self._play = None self._last_task_banner = None + self._last_task_name = None super(CallbackModule, self).__init__() def v2_runner_on_failed(self, result, ignore_errors=False): @@ -46,10 +47,12 @@ class CallbackModule(CallbackBase): delegated_vars = result._result.get('_ansible_delegated_vars', None) self._clean_results(result._result, result._task.action) - if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid: + if self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) - self._handle_exception(result._result) + use_stderr = self._plugin_options.get('display_failed_stderr', False) + + self._handle_exception(result._result, use_stderr=use_stderr) self._handle_warnings(result._result) if result._task.loop and 'results' in result._result: @@ -58,18 +61,22 @@ class CallbackModule(CallbackBase): else: if delegated_vars: self._display.display("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'], - self._dump_results(result._result)), color=C.COLOR_ERROR) + self._dump_results(result._result)), + color=C.COLOR_ERROR, stderr=use_stderr) else: - self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)), color=C.COLOR_ERROR) + self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)), + color=C.COLOR_ERROR, stderr=use_stderr) if ignore_errors: self._display.display("...ignoring", color=C.COLOR_SKIP) def v2_runner_on_ok(self, result): + if not self._plugin_options.get('display_ok_hosts'): + return delegated_vars = result._result.get('_ansible_delegated_vars', None) - if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid: + if self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) if isinstance(result._task, TaskInclude): @@ -99,11 +106,11 @@ class CallbackModule(CallbackBase): self._display.display(msg, color=color) def v2_runner_on_skipped(self, result): - if self._plugin_options.get('show_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS): # fallback on constants for inherited plugins missing docs + if self._plugin_options.get('display_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS): # fallback on constants for inherited plugins missing docs self._clean_results(result._result, result._task.action) - if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid: + if self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) if result._task.loop and 'results' in result._result: @@ -115,7 +122,7 @@ class CallbackModule(CallbackBase): self._display.display(msg, color=C.COLOR_SKIP) def v2_runner_on_unreachable(self, result): - if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid: + if self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) delegated_vars = result._result.get('_ansible_delegated_vars', None) @@ -133,9 +140,14 @@ class CallbackModule(CallbackBase): self._display.banner("NO MORE HOSTS LEFT") def v2_playbook_on_task_start(self, task, is_conditional): - + # Preserve task name, as all vars may not be available for templating + # when we need it later if self._play.strategy != 'free': - self._print_task_banner(task) + self._last_task_name = task.get_name().strip() + + # Display the task banner immediately if we're not doing any filtering based on task result + if self._plugin_options.get('display_skipped_hosts') and self._plugin_options.get('display_ok_hosts'): + self._print_task_banner(task) def _print_task_banner(self, task): # args can be specified as no_log in several places: in the task or in @@ -151,7 +163,12 @@ class CallbackModule(CallbackBase): args = u', '.join(u'%s=%s' % a for a in task.args.items()) args = u' %s' % args - self._display.banner(u"TASK [%s%s]" % (task.get_name().strip(), args)) + # Use cached task name + task_name = self._last_task_name + if task_name is None: + task_name = task.get_name().strip() + + self._display.banner(u"TASK [%s%s]" % (task_name, args)) if self._display.verbosity >= 2: path = task.get_path() if path: @@ -189,6 +206,9 @@ class CallbackModule(CallbackBase): self._display.display(diff) def v2_runner_item_on_ok(self, result): + if not self._plugin_options.get('display_ok_hosts'): + return + delegated_vars = result._result.get('_ansible_delegated_vars', None) self._clean_results(result._result, result._task.action) if isinstance(result._task, TaskInclude): @@ -227,7 +247,7 @@ class CallbackModule(CallbackBase): self._display.display(msg + " (item=%s) => %s" % (self._get_item_label(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR) def v2_runner_item_on_skipped(self, result): - if self._plugin_options.get('show_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS): # fallback on constants for inherited plugins missing docs + if self._plugin_options.get('display_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS): # fallback on constants for inherited plugins missing docs self._clean_results(result._result, result._task.action) msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item_label(result._result)) if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result: diff --git a/lib/ansible/plugins/callback/full_skip.py b/lib/ansible/plugins/callback/full_skip.py index 6db4260b74..f56435ed64 100644 --- a/lib/ansible/plugins/callback/full_skip.py +++ b/lib/ansible/plugins/callback/full_skip.py @@ -13,6 +13,10 @@ DOCUMENTATION = ''' description: - Use this plugin when you dont care about any output for tasks that were completly skipped version_added: "2.4" + deprecated: + why: The 'default' callback plugin now supports this functionality + removed_in: '2.11' + alternative: "'default' callback plugin with 'display_skipped_hosts = no' option" extends_documentation_fragment: - default_callback requirements: diff --git a/lib/ansible/plugins/callback/skippy.py b/lib/ansible/plugins/callback/skippy.py index 92076cc146..e5fa1c1bb2 100644 --- a/lib/ansible/plugins/callback/skippy.py +++ b/lib/ansible/plugins/callback/skippy.py @@ -13,6 +13,10 @@ DOCUMENTATION = ''' - set as main display callback short_description: Ansible screen output that ignores skipped status version_added: "2.0" + deprecated: + why: The 'default' callback plugin now supports this functionality + removed_in: '2.11' + alternative: "'default' callback plugin with 'display_skipped_hosts = no' option" extends_documentation_fragment: - default_callback description: diff --git a/lib/ansible/plugins/callback/stderr.py b/lib/ansible/plugins/callback/stderr.py index 9d8240cb1b..19ba53b508 100644 --- a/lib/ansible/plugins/callback/stderr.py +++ b/lib/ansible/plugins/callback/stderr.py @@ -13,6 +13,10 @@ DOCUMENTATION = ''' - set as main display callback short_description: Splits output, sending failed tasks to stderr version_added: "2.4" + deprecated: + why: The 'default' callback plugin now supports this functionality + removed_in: '2.11' + alternative: "'default' callback plugin with 'display_failed_stderr = yes' option" extends_documentation_fragment: - default_callback description: @@ -48,7 +52,7 @@ class CallbackModule(CallbackModule_default): if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid: self._print_task_banner(result._task) - self._handle_exception(result._result, errors_to_stderr=True) + self._handle_exception(result._result, use_stderr=True) self._handle_warnings(result._result) if result._task.loop and 'results' in result._result: @@ -65,17 +69,3 @@ class CallbackModule(CallbackModule_default): if ignore_errors: self._display.display("...ignoring", color=C.COLOR_SKIP) - - def _handle_exception(self, result, errors_to_stderr=False): - - if 'exception' in result: - msg = "An exception occurred during task execution. " - if self._display.verbosity < 3: - # extract just the actual error message from the exception text - error = result['exception'].strip().split('\n')[-1] - msg += "To see the full traceback, use -vvv. The error was: %s" % error - else: - msg = "The full traceback is:\n" + result['exception'] - del result['exception'] - - self._display.display(msg, color=C.COLOR_ERROR, stderr=errors_to_stderr) diff --git a/lib/ansible/utils/module_docs_fragments/default_callback.py b/lib/ansible/utils/module_docs_fragments/default_callback.py index 9cca95e9fa..4a343511cd 100644 --- a/lib/ansible/utils/module_docs_fragments/default_callback.py +++ b/lib/ansible/utils/module_docs_fragments/default_callback.py @@ -6,7 +6,7 @@ class ModuleDocFragment(object): DOCUMENTATION = """ options: - show_skipped_hosts: + display_skipped_hosts: name: Show skipped hosts description: "Toggle to control displaying skipped task/host results in a task" default: True @@ -16,6 +16,28 @@ class ModuleDocFragment(object): - key: display_skipped_hosts section: defaults type: boolean + display_ok_hosts: + name: Show 'ok' hosts + description: "Toggle to control displaying 'ok' task/host results in a task" + default: True + env: + - name: ANSIBLE_DISPLAY_OK_HOSTS + ini: + - key: display_ok_hosts + section: defaults + type: boolean + version_added: '2.7' + display_failed_stderr: + name: Use STDERR for failed tasks + description: "Toggle to control whether failed tasks are displayed to STDERR (vs. STDOUT)" + default: False + env: + - name: ANSIBLE_DISPLAY_FAILED_STDERR + ini: + - key: display_failed_stderr + section: defaults + type: boolean + version_added: '2.7' show_custom_stats: name: Show custom stats description: 'This adds the custom stats set via the set_stats plugin to the play recap'