mirror of https://github.com/jumpserver/jumpserver
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
9.7 KiB
325 lines
9.7 KiB
# ~*~ 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) |
|
|
|
|
|
|
|
|