# ~*~ coding: utf-8 ~*~ import datetime import json import os from collections import defaultdict import ansible.constants as C from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule from ansible.plugins.callback.minimal import CallbackModule as CMDCallBackModule from common.utils.strings import safe_str class CallbackMixin: def __init__(self, display=None): # result_raw example: { # "ok": {"hostname": {"task_name": {},...},..}, # "failed": {"hostname": {"task_name": {}..}, ..}, # "unreachable: {"hostname": {"task_name": {}, ..}}, # "skipped": {"hostname": {"task_name": {}, ..}, ..}, # } # results_summary example: { # "contacted": {"hostname": {"task_name": {}}, "hostname": {}}, # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,}, # "success": True # } self.results_raw = dict( ok=defaultdict(dict), failed=defaultdict(dict), unreachable=defaultdict(dict), skippe=defaultdict(dict), ) self.results_summary = dict( contacted=defaultdict(dict), dark=defaultdict(dict), success=True ) self.results = { 'raw': self.results_raw, 'summary': self.results_summary, } super().__init__() if display: self._display = display cols = os.environ.get("TERM_COLS", None) self._display.columns = 79 if cols and cols.isdigit(): self._display.columns = int(cols) - 1 def display(self, msg): self._display.display(msg) def gather_result(self, t, result): self._clean_results(result._result, result._task.action) host = result._host.get_name() task_name = result.task_name task_result = result._result self.results_raw[t][host][task_name] = task_result self.clean_result(t, host, task_name, task_result) def close(self): if hasattr(self._display, 'close'): self._display.close() class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): """ Task result Callback """ context = None def clean_result(self, t, host, task_name, task_result): contacted = self.results_summary["contacted"] dark = self.results_summary["dark"] if task_result.get('rc') is not None: cmd = task_result.get('cmd') if isinstance(cmd, list): cmd = " ".join(cmd) else: cmd = str(cmd) detail = { 'cmd': cmd, 'stderr': task_result.get('stderr'), 'stdout': safe_str(str(task_result.get('stdout', ''))), 'rc': task_result.get('rc'), 'delta': task_result.get('delta'), 'msg': task_result.get('msg', '') } else: detail = { "changed": task_result.get('changed', False), "msg": task_result.get('msg', '') } if t in ("ok", "skipped"): contacted[host][task_name] = detail else: dark[host][task_name] = detail def v2_runner_on_failed(self, result, ignore_errors=False): self.results_summary['success'] = False self.gather_result("failed", result) if result._task.action in C.MODULE_NO_JSON: CMDCallBackModule.v2_runner_on_failed(self, result, ignore_errors=ignore_errors ) else: super().v2_runner_on_failed( result, ignore_errors=ignore_errors ) def v2_runner_on_ok(self, result): self.gather_result("ok", result) if result._task.action in C.MODULE_NO_JSON: CMDCallBackModule.v2_runner_on_ok(self, result) else: super().v2_runner_on_ok(result) def v2_runner_on_skipped(self, result): self.gather_result("skipped", result) super().v2_runner_on_skipped(result) def v2_runner_on_unreachable(self, result): self.results_summary['success'] = False self.gather_result("unreachable", result) super().v2_runner_on_unreachable(result) def v2_runner_on_start(self, *args, **kwargs): pass def display_skipped_hosts(self): pass def display_ok_hosts(self): pass def display_failed_stderr(self): pass def set_play_context(self, context): # for k, v in context._attributes.items(): # print("{} ==> {}".format(k, v)) if self.context and isinstance(self.context, dict): for k, v in self.context.items(): setattr(context, k, v) class CommandResultCallback(AdHocResultCallback): """ Command result callback results_command: { "cmd": "", "stderr": "", "stdout": "", "rc": 0, "delta": 0:0:0.123 } """ def __init__(self, display=None, **kwargs): self.results_command = dict() super().__init__(display) def gather_result(self, t, res): super().gather_result(t, res) self.gather_cmd(t, res) def v2_playbook_on_play_start(self, play): now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') msg = '$ {} ({})'.format(play.name, now) self._play = play self._display.banner(msg) def v2_runner_on_unreachable(self, result): self.results_summary['success'] = False self.gather_result("unreachable", result) msg = result._result.get("msg") if not msg: msg = json.dumps(result._result, indent=4) self._display.display("%s | FAILED! => \n%s" % ( result._host.get_name(), msg, ), color=C.COLOR_ERROR) def v2_runner_on_failed(self, result, ignore_errors=False): self.results_summary['success'] = False self.gather_result("failed", result) msg = result._result.get("msg", '') stderr = result._result.get("stderr") if stderr: msg += '\n' + stderr module_stdout = result._result.get("module_stdout") if module_stdout: msg += '\n' + module_stdout if not msg: msg = json.dumps(result._result, indent=4) self._display.display("%s | FAILED! => \n%s" % ( result._host.get_name(), msg, ), color=C.COLOR_ERROR) def v2_playbook_on_stats(self, stats): pass def _print_task_banner(self, task): pass def gather_cmd(self, t, res): host = res._host.get_name() cmd = {} if t == "ok": cmd['cmd'] = res._result.get('cmd') cmd['stderr'] = res._result.get('stderr') cmd['stdout'] = safe_str(str(res._result.get('stdout', ''))) cmd['rc'] = res._result.get('rc') cmd['delta'] = res._result.get('delta') else: cmd['err'] = "Error: {}".format(res) self.results_command[host] = cmd class PlaybookResultCallBack(CallbackBase): """ Custom callback model for handlering the output data of execute playbook file, Base on the build-in callback plugins of ansible which named `json`. """ CALLBACK_VERSION = 2.0 CALLBACK_TYPE = 'stdout' CALLBACK_NAME = 'Dict' def __init__(self, display=None): super(PlaybookResultCallBack, self).__init__(display) self.results = [] self.output = "" self.item_results = {} # {"host": []} def _new_play(self, play): return { 'play': { 'name': play.name, 'id': str(play._uuid) }, 'tasks': [] } def _new_task(self, task): return { 'task': { 'name': task.get_name(), }, 'hosts': {} } def v2_playbook_on_no_hosts_matched(self): self.output = "skipping: No match hosts." def v2_playbook_on_no_hosts_remaining(self): pass def v2_playbook_on_task_start(self, task, is_conditional): self.results[-1]['tasks'].append(self._new_task(task)) def v2_playbook_on_play_start(self, play): self.results.append(self._new_play(play)) def v2_playbook_on_stats(self, stats): hosts = sorted(stats.processed.keys()) summary = {} for h in hosts: s = stats.summarize(h) summary[h] = s if self.output: pass else: self.output = { 'plays': self.results, 'stats': summary } def gather_result(self, res): if res._task.loop and "results" in res._result and res._host.name in self.item_results: res._result.update({"results": self.item_results[res._host.name]}) del self.item_results[res._host.name] self.results[-1]['tasks'][-1]['hosts'][res._host.name] = res._result def v2_runner_on_ok(self, res, **kwargs): if "ansible_facts" in res._result: del res._result["ansible_facts"] self.gather_result(res) def v2_runner_on_failed(self, res, **kwargs): self.gather_result(res) def v2_runner_on_unreachable(self, res, **kwargs): self.gather_result(res) def v2_runner_on_skipped(self, res, **kwargs): self.gather_result(res) def gather_item_result(self, res): self.item_results.setdefault(res._host.name, []).append(res._result) def v2_runner_item_on_ok(self, res): self.gather_item_result(res) def v2_runner_item_on_failed(self, res): self.gather_item_result(res) def v2_runner_item_on_skipped(self, res): self.gather_item_result(res)