diff --git a/callback_plugins/.keep b/callback_plugins/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/callback_plugins/fail_if_no_hosts.py b/callback_plugins/fail_if_no_hosts.py deleted file mode 100644 index 8b60c17cf..000000000 --- a/callback_plugins/fail_if_no_hosts.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python - -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys - -from ansible.plugins.callback import CallbackBase - - -class CallbackModule(CallbackBase): - CALLBACK_VERSION = 2.0 - CALLBACK_NAME = 'fail_if_no_hosts' - - def __init__(self, display=None): - super(CallbackModule, self).__init__(display) - - def v2_playbook_on_stats(self, stats): - if len(stats.processed.keys()) == 0: - sys.exit(10) diff --git a/callback_plugins/validation_json.py b/callback_plugins/validation_json.py deleted file mode 100644 index fffbc101a..000000000 --- a/callback_plugins/validation_json.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python - -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -__metaclass__ = type - -import datetime -import json -import time -import os - -from functools import partial - -from ansible.module_utils.six.moves import reduce -from ansible.parsing.ajson import AnsibleJSONEncoder -from ansible.plugins.callback import CallbackBase - -DOCUMENTATION = ''' - callback: json - short_description: Ansible screen output as JSON file - version_added: "1.0" - description: - - This callback converts all events into a JSON file - stored in /var/log/validations - type: stdout - requirements: None -''' - -VALIDATIONS_LOG_DIR = "/var/log/validations" - - -def current_time(): - return '%sZ' % datetime.datetime.utcnow().isoformat() - - -def secondsToStr(t): - def rediv(ll, b): - return list(divmod(ll[0], b)) + ll[1:] - - return "%d:%02d:%02d.%03d" % tuple( - reduce(rediv, [[ - t * 1000, - ], 1000, 60, 60])) - - -class CallbackModule(CallbackBase): - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'stdout' - CALLBACK_NAME = 'validation_json' - - def __init__(self, display=None): - super(CallbackModule, self).__init__(display) - self.results = [] - self.simple_results = [] - self.env = {} - self.t0 = None - self.current_time = current_time() - - def _new_play(self, play): - return { - 'play': { - 'host': play.get_name(), - 'validation_id': self.env['playbook_name'], - 'validation_path': self.env['playbook_path'], - 'id': (os.getenv('ANSIBLE_UUID') if os.getenv('ANSIBLE_UUID') - else str(play._uuid)), - 'duration': { - 'start': current_time() - } - }, - 'tasks': [] - } - - def _new_task(self, task): - return { - 'task': { - 'name': task.get_name(), - 'id': str(task._uuid), - 'duration': { - 'start': current_time() - } - }, - 'hosts': {} - } - - def _val_task(self, task_name): - return { - 'task': { - 'name': task_name, - 'hosts': {} - } - } - - def _val_task_host(self, task_name): - return { - 'task': { - 'name': task_name, - 'hosts': {} - } - } - - def v2_playbook_on_start(self, playbook): - self.t0 = time.time() - pl = playbook._file_name - validation_id = os.path.splitext(os.path.basename(pl))[0] - self.env = { - "playbook_name": validation_id, - "playbook_path": playbook._basedir - } - - def v2_playbook_on_play_start(self, play): - self.results.append(self._new_play(play)) - - def v2_playbook_on_task_start(self, task, is_conditional): - self.results[-1]['tasks'].append(self._new_task(task)) - - def v2_playbook_on_handler_task_start(self, task): - self.results[-1]['tasks'].append(self._new_task(task)) - - def v2_playbook_on_stats(self, stats): - """Display info about playbook statistics""" - - hosts = sorted(stats.processed.keys()) - - summary = {} - for h in hosts: - s = stats.summarize(h) - summary[h] = s - - output = { - 'plays': self.results, - 'stats': summary, - 'validation_output': self.simple_results - } - - log_file = "{}/{}_{}_{}.json".format( - VALIDATIONS_LOG_DIR, - (os.getenv('ANSIBLE_UUID') if os.getenv('ANSIBLE_UUID') else - self.results[0].get('play').get('id')), - self.env['playbook_name'], - self.current_time) - - with open(log_file, 'w') as js: - js.write(json.dumps(output, - cls=AnsibleJSONEncoder, - indent=4, - sort_keys=True)) - - def _record_task_result(self, on_info, result, **kwargs): - """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 - - if 'failed' in task_result.keys(): - self.simple_results.append(self._val_task(task.name)) - self.simple_results[-1]['task']['status'] = "FAILED" - self.simple_results[-1]['task']['hosts'][host.name] = task_result - if 'warnings' in task_result.keys(): - self.simple_results.append(self._val_task(task.name)) - self.simple_results[-1]['task']['status'] = "WARNING" - self.simple_results[-1]['task']['hosts'][host.name] = task_result - - end_time = current_time() - time_elapsed = secondsToStr(time.time() - self.t0) - self.results[-1]['tasks'][-1]['task']['duration']['end'] = end_time - self.results[-1]['play']['duration']['end'] = end_time - self.results[-1]['play']['duration']['time_elapsed'] = time_elapsed - - 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'): - return object.__getattribute__(self, name) - - on = name.rsplit('_', 1)[1] - - on_info = {} - if on in ('failed', 'skipped'): - on_info[on] = True - - return partial(self._record_task_result, on_info) diff --git a/callback_plugins/validation_output.py b/callback_plugins/validation_output.py deleted file mode 100644 index 37f4a817c..000000000 --- a/callback_plugins/validation_output.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python - -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pprint - -from ansible import constants as C -from ansible.plugins.callback import CallbackBase - - -FAILURE_TEMPLATE = """\ -Task '{}' failed: -Host: {} -Message: {} -""" - -WARNING_TEMPLATE = """\ -Task '{}' succeeded, but had some warnings: -Host: {} -Warnings: {} -""" - -DEBUG_TEMPLATE = """\ -Task: Debug -Host: {} -{} -""" - - -def indent(text): - '''Indent the given text by four spaces.''' - return ''.join(' {}\n'.format(line) for line in text.splitlines()) - - -# TODO(shadower): test with async settings -class CallbackModule(CallbackBase): - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'stdout' - CALLBACK_NAME = 'validation_output' - - def __init__(self, display=None): - super(CallbackModule, self).__init__(display) - - def print_failure_message(self, host_name, task_name, results, - abridged_result): - '''Print a human-readable error info from Ansible result dictionary.''' - - def is_script(results): - return ('rc' in results and 'invocation' in results - and 'script' in results._task_fields['action'] - and '_raw_params' in results._task_fields['args']) - - display_full_results = False - if 'rc' in results and 'cmd' in results: - command = results['cmd'] - # The command can be either a list or a string. - # Concat if it's a list: - if type(command) == list: - command = " ".join(results['cmd']) - message = "Command `{}` exited with code: {}".format( - command, results['rc']) - # There may be an optional message attached to the command. - # Display it: - if 'msg' in results: - message = message + ": " + results['msg'] - elif is_script(results): - script_name = results['invocation']['module_args']['_raw_params'] - message = "Script `{}` exited with code: {}".format( - script_name, results['rc']) - elif 'msg' in results: - message = results['msg'] - else: - message = "Unknown error" - display_full_results = True - - self._display.display( - FAILURE_TEMPLATE.format(task_name, host_name, message), - color=C.COLOR_ERROR) - - stdout = results.get('module_stdout', results.get('stdout', '')) - if stdout: - print('stdout:') - self._display.display(indent(stdout), color=C.COLOR_ERROR) - stderr = results.get('module_stderr', results.get('stderr', '')) - if stderr: - print('stderr:') - self._display.display(indent(stderr), color=C.COLOR_ERROR) - if display_full_results: - print( - "Could not get an error message. Here is the Ansible output:") - pprint.pprint(abridged_result, indent=4) - warnings = results.get('warnings', []) - if warnings: - print("Warnings:") - for warning in warnings: - self._display.display("* %s " % warning, color=C.COLOR_WARN) - print("") - - def v2_playbook_on_play_start(self, play): - pass # No need to notify that a play started - - def v2_playbook_on_task_start(self, task, is_conditional): - pass # No need to notify that a task started - - def v2_runner_on_ok(self, result, **kwargs): - host_name = result._host - task_name = result._task.get_name() - task_fields = result._task_fields - results = result._result # A dict of the module name etc. - self._dump_results(results) - warnings = results.get('warnings', []) - # Print only tasks that produced some warnings: - if warnings: - for warning in warnings: - warn_msg = "{}\n".format(warning) - self._display.display(WARNING_TEMPLATE.format(task_name, - host_name, - warn_msg), - color=C.COLOR_WARN) - - if 'debug' in task_fields['action']: - output = "" - - if 'var' in task_fields['args']: - variable = task_fields['args']['var'] - value = results[variable] - output = "{}: {}".format(variable, str(value)) - elif 'msg' in task_fields['args']: - output = "Message: {}".format( - task_fields['args']['msg']) - - self._display.display(DEBUG_TEMPLATE.format(host_name, output), - color=C.COLOR_OK) - - def v2_runner_on_failed(self, result, **kwargs): - host_name = result._host - task_name = result._task.get_name() - - result_dict = result._result # A dict of the module name etc. - abridged_result = self._dump_results(result_dict) - - if 'results' in result_dict: - # The task is a list of items under `results` - for item in result_dict['results']: - if item.get('failed', False): - self.print_failure_message(host_name, task_name, - item, item) - else: - # The task is a "normal" module invocation - self.print_failure_message(host_name, task_name, result_dict, - abridged_result) - - def v2_runner_on_skipped(self, result, **kwargs): - pass # No need to print skipped tasks - - def v2_runner_on_unreachable(self, result, **kwargs): - host_name = result._host - task_name = result._task.get_name() - results = {'msg': 'The host is unreachable.'} - self.print_failure_message(host_name, task_name, results, results) - - def v2_playbook_on_stats(self, stats): - def failed(host): - _failures = stats.summarize(host).get('failures', 0) > 0 - _unreachable = stats.summarize(host).get('unreachable', 0) > 0 - return (_failures or _unreachable) - - hosts = sorted(stats.processed.keys()) - failed_hosts = [host for host in hosts if failed(host)] - - if hosts: - if failed_hosts: - if len(failed_hosts) == len(hosts): - print("Failure! The validation failed for all hosts:") - for failed_host in failed_hosts: - self._display.display("* %s" % failed_host, - color=C.COLOR_ERROR) - else: - print("Failure! The validation failed for hosts:") - for failed_host in failed_hosts: - self._display.display("* %s" % failed_host, - color=C.COLOR_ERROR) - print("and passed for hosts:") - for host in [h for h in hosts if h not in failed_hosts]: - self._display.display("* %s" % host, - color=C.COLOR_OK) - else: - print("Success! The validation passed for all hosts:") - for host in hosts: - self._display.display("* %s" % host, - color=C.COLOR_OK) - else: - print("Warning! The validation did not run on any host.")