import os from collections import defaultdict from functools import reduce from django.conf import settings class DefaultCallback: STATUS_MAPPER = { "successful": "success", "failure": "failed", "failed": "failed", "running": "running", "pending": "pending", "timeout": "timeout", "unknown": "unknown", } def __init__(self): self.result = dict( ok=defaultdict(dict), failures=defaultdict(dict), dark=defaultdict(dict), skipped=defaultdict(dict), ignored=defaultdict(dict), ) self.summary = dict( ok=[], failures={}, dark={}, skipped=[], ) self.status = "running" self.finished = False self.local_pid = 0 self.private_data_dir = None @property def host_results(self): results = defaultdict(dict) for state, hosts in self.result.items(): for host, items in hosts.items(): results[host][state] = items return results def is_success(self): return self.status != "success" def event_handler(self, data, **kwargs): event = data.get("event", None) if not event: return pid = data.get("pid", None) if pid: self.write_pid(pid) event_data = data.get("event_data", {}) host = event_data.get("remote_addr", "") task = event_data.get("task", "") res = event_data.get("res", {}) handler = getattr(self, event, self.on_any) handler(event_data, host=host, task=task, res=res) def runner_on_ok(self, event_data, host=None, task=None, res=None): detail = { "action": event_data.get("task_action", ""), "res": res, "rc": res.get("rc", 0), "stdout": res.get("stdout", ""), } self.result["ok"][host][task] = detail def runner_on_skipped(self, event_data, host=None, task=None, **kwargs): detail = { "action": event_data.get("task_action", ""), "res": {}, "rc": 0, } self.result["skipped"][host][task] = detail def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs): detail = { "action": event_data.get("task_action", ""), "res": res, "rc": res.get("rc", 0), "stdout": res.get("stdout", ""), "stderr": ";".join([res.get("stderr", ""), res.get("msg", "")]).strip(";"), } ignore_errors = event_data.get("ignore_errors", False) error_key = "ignored" if ignore_errors else "failures" self.result[error_key][host][task] = detail def runner_on_unreachable( self, event_data, host=None, task=None, res=None, **kwargs ): detail = { "action": event_data.get("task_action", ""), "res": res, "rc": 255, "stderr": ";".join([res.get("stderr", ""), res.get("msg", "")]).strip(";"), } self.result["dark"][host][task] = detail def runner_on_start(self, event_data, **kwargs): pass def runner_retry(self, event_data, **kwargs): pass def runner_on_file_diff(self, event_data, **kwargs): pass def runner_item_on_failed(self, event_data, **kwargs): pass def runner_item_on_skipped(self, event_data, **kwargs): pass def playbook_on_play_start(self, event_data, **kwargs): pass def playbook_on_stats(self, event_data, **kwargs): error_func = ( lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};" ) for tp in ["dark", "failures"]: for host, tasks in self.result[tp].items(): error = reduce(error_func, tasks.items(), "").strip(";") self.summary[tp][host] = error failures = list(self.result["failures"].keys()) dark_or_failures = list(self.result["dark"].keys()) + failures for host, tasks in self.result.get("ignored", {}).items(): ignore_errors = reduce(error_func, tasks.items(), "").strip(";") if host in failures: self.summary["failures"][host] += ignore_errors self.summary["ok"] = list(set(self.result["ok"].keys()) - set(dark_or_failures)) self.summary["skipped"] = list( set(self.result["skipped"].keys()) - set(dark_or_failures) ) def playbook_on_include(self, event_data, **kwargs): pass def playbook_on_notify(self, event_data, **kwargs): pass def playbook_on_vars_prompt(self, event_data, **kwargs): pass def playbook_on_handler_task_start(self, event_data, **kwargs): pass def playbook_on_no_hosts_matched(self, event_data, **kwargs): pass def playbook_on_no_hosts_remaining(self, event_data, **kwargs): pass def playbook_on_start(self, event_data, **kwargs): if settings.DEBUG_DEV: print("DEBUG: delete inventory: ", os.path.join(self.private_data_dir, 'inventory')) inventory_path = os.path.join(self.private_data_dir, 'inventory', 'hosts') if os.path.exists(inventory_path): os.remove(inventory_path) def warning(self, event_data, **kwargs): pass def on_any(self, event_data, **kwargs): pass def status_handler(self, data, **kwargs): status = data.get("status", "") self.status = self.STATUS_MAPPER.get(status, "unknown") self.private_data_dir = data.get("private_data_dir", None) def write_pid(self, pid): pid_filepath = os.path.join(self.private_data_dir, "local.pid") with open(pid_filepath, "w") as f: f.write(str(pid))