From 095560771cfd8300aa833561d7d9f9cd53de68a1 Mon Sep 17 00:00:00 2001 From: fredericve Date: Fri, 28 Jul 2017 19:50:15 +0200 Subject: [PATCH] Fixes #17958: use stderr instead of the default stdout for fatal errors (#17962) * add a callback plugin that sends failures to stderr * fix warnings --- lib/ansible/plugins/callback/stderr.py | 80 ++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/ansible/plugins/callback/stderr.py diff --git a/lib/ansible/plugins/callback/stderr.py b/lib/ansible/plugins/callback/stderr.py new file mode 100644 index 00000000000..98e469a0e31 --- /dev/null +++ b/lib/ansible/plugins/callback/stderr.py @@ -0,0 +1,80 @@ +# (c) 2017, Frederic Van Espen +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible import constants as C +from ansible.plugins.callback.default import CallbackModule as CallbackModule_default + + +class CallbackModule(CallbackModule_default): + + ''' + This is the stderr callback plugin, which reuses the default + callback plugin but sends error output to stderr. + ''' + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'stdout' + CALLBACK_NAME = 'stderr' + + def __init__(self): + + self.super_ref = super(CallbackModule, self) + self.super_ref.__init__() + + def v2_runner_on_failed(self, result, ignore_errors=False): + + 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: + self._print_task_banner(result._task) + + self._handle_exception(result._result, errors_to_stderr=True) + self._handle_warnings(result._result) + + if result._task.loop and 'results' in result._result: + self._process_items(result) + + 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, + stderr=True) + else: + self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)), + color=C.COLOR_ERROR, stderr=True) + + 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)