diff --git a/parser.py b/parser.py index f28084c..00e05fa 100644 --- a/parser.py +++ b/parser.py @@ -1,51 +1,87 @@ import re import sys +from collections import defaultdict def parse_file(file_path): - results = [] + recap_results = [] + failed_tasks = defaultdict(list) with open(file_path, "r", encoding="utf-8", errors="ignore") as f: - for line in f: + lines = f.readlines() - # Look for lines containing recap info - if "ok=" in line and "failed=" in line and "unreachable=" in line: + current_task = None - # Extract host (first non-time token before :) - host_match = re.search(r"(?:\d{2}:\d{2}:\d{2}\s+)?(\S+)\s+:", line) - if not host_match: - continue + for i, line in enumerate(lines): - host = host_match.group(1) + # ------------------------- + # 1. Detect TASK name + # ------------------------- + task_match = re.search(r"TASK \[(.*?)\]", line) + if task_match: + current_task = task_match.group(1) - # Extract values individually (order-independent) - def get_value(field): - match = re.search(rf"{field}=(\d+)", line) - return int(match.group(1)) if match else 0 + # ------------------------- + # 2. Detect FAILED lines + # ------------------------- + fail_match = re.search(r"fatal: \[(.*?)\]: FAILED!", line) + if fail_match: + host = fail_match.group(1) - ok = get_value("ok") - changed = get_value("changed") - unreachable = get_value("unreachable") - failed = get_value("failed") + # Try to grab error message from same or next lines + error_msg = "" - status = ( - "UNREACHABLE" if unreachable > 0 - else "FAILED" if failed > 0 - else "OK" - ) + # Look ahead a few lines to capture reason + for j in range(i, min(i + 5, len(lines))): + if "msg" in lines[j] or "stderr" in lines[j]: + error_msg += lines[j].strip() + " " - results.append({ - "host": host, - "ok": ok, - "changed": changed, - "unreachable": unreachable, - "failed": failed, - "status": status - }) + if not error_msg: + error_msg = "Unknown error" - return results + failed_tasks[host].append({ + "task": current_task, + "error": error_msg.strip() + }) + + # ------------------------- + # 3. Parse PLAY RECAP lines + # ------------------------- + if "ok=" in line and "failed=" in line and "unreachable=" in line: + + host_match = re.search(r"(?:\d{2}:\d{2}:\d{2}\s+)?(\S+)\s+:", line) + if not host_match: + continue + + host = host_match.group(1) + + def get_value(field): + match = re.search(rf"{field}=(\d+)", line) + return int(match.group(1)) if match else 0 + + ok = get_value("ok") + changed = get_value("changed") + unreachable = get_value("unreachable") + failed = get_value("failed") + + status = ( + "UNREACHABLE" if unreachable > 0 + else "FAILED" if failed > 0 + else "OK" + ) + + recap_results.append({ + "host": host, + "ok": ok, + "changed": changed, + "unreachable": unreachable, + "failed": failed, + "status": status + }) + + return recap_results, failed_tasks -def print_report(results): +def print_report(results, failed_tasks): unreachable_hosts = [] failed_hosts = [] @@ -68,7 +104,22 @@ def print_report(results): print("\nFAILED HOSTS:") print("\n".join(failed_hosts) if failed_hosts else " None") + # ------------------------- + # NEW SECTION + # ------------------------- + print("\n=== FAILED TASK DETAILS ===") + + if not failed_tasks: + print(" No failed tasks detected") + return + + for host, tasks in failed_tasks.items(): + print(f"\n{host}") + for t in tasks: + print(f" Task: {t['task']}") + print(f" Error: {t['error']}") + if __name__ == "__main__": - results = parse_file(sys.argv[1]) - print_report(results) + results, failed_tasks = parse_file(sys.argv[1]) + print_report(results, failed_tasks) \ No newline at end of file