From 7181848c6500728e16a53c78c25d62ae8e6e00c9 Mon Sep 17 00:00:00 2001 From: badra001 Date: Tue, 13 Jan 2026 11:40:13 -0500 Subject: [PATCH] output to file (needs fixing) --- .../cross-organization/assess_check_ecr.py | 83 +++++++++---------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/local-app/python-tools/cross-organization/assess_check_ecr.py b/local-app/python-tools/cross-organization/assess_check_ecr.py index 595ec9f5..d0b12c94 100755 --- a/local-app/python-tools/cross-organization/assess_check_ecr.py +++ b/local-app/python-tools/cross-organization/assess_check_ecr.py @@ -1,14 +1,12 @@ #!/usr/bin/env python - import json, argparse, sys, os, glob from datetime import datetime, timezone from collections import Counter, defaultdict # --- VERSIONING --- -__version__ = "1.3.3" +__version__ = "1.3.4" def find_latest_file(pattern): - """Locates the most recent ECR audit JSON file.""" files = glob.glob("audit_results.check_ecr.*.json") return max(files, key=os.path.getctime) if files else None @@ -30,21 +28,31 @@ def bucket_age(days, counters): else: counters['<30'] += 1 def main(): - parser = argparse.ArgumentParser(description="ECR Full Spectrum Assessor - v1.3.3") + parser = argparse.ArgumentParser(description="ECR Full Spectrum Assessor & Reporter") parser.add_argument("--input", help="JSON audit file") args = parser.parse_args() - input_file = args.input or find_latest_file("audit_results.check_ecr.*.json") + input_file = args.input or find_latest_file() if not input_file: print("Error: No file found."); sys.exit(1) + # Generate the output filename + base_name, _ = os.path.splitext(input_file) + output_file = f"{base_name}_results.txt" + with open(input_file, 'r') as f: data = json.load(f) + # We use a custom print function to output to both screen and file + report_file = open(output_file, 'w') + def report_print(msg=""): + print(msg) + report_file.write(str(msg) + "\n") + report_width = 240 - print("-" * report_width) - print(f"ECR COMPREHENSIVE AUDIT | Accounts: {len(data)} | Input: {os.path.basename(input_file)}") - print("-" * report_width) - print(f"{'Idx':<4} | {'Account ID':<15} | {'Region':<12} | {'Repo Name':<40} | {'Size (GB)':<10} | {'Mutability':<11} | {'L/Cycle':<7} | {'CRITICAL':<8} | {'HIGH':<8}") - print("-" * report_width) + report_print("-" * report_width) + report_print(f"ECR COMPREHENSIVE AUDIT & REPORT | Accounts: {len(data)} | Input: {input_file}") + report_print("-" * report_width) + report_print(f"{'Idx':<4} | {'Account ID':<15} | {'Region':<12} | {'Repo Name':<40} | {'Size (GB)':<10} | {'Mutability':<11} | {'L/Cycle':<7} | {'CRITICAL':<8} | {'HIGH':<8}") + report_print("-" * report_width) stats = { "total_repos": 0, "total_images": 0, "total_bytes": 0, "no_lc": 0, @@ -54,7 +62,6 @@ def main(): "total_push_days": 0, "push_count": 0, "org_vulns": Counter(), "scanned_imgs": 0 } - digest_tracker = Counter() for idx, account in enumerate(data, 1): @@ -63,32 +70,24 @@ def main(): if key == "account_summary" or ":" not in key: continue region = key.split(":")[0] stats["total_repos"] += 1 - - repo_mut = val.get("mutability", "MUTABLE") + repo_mut, has_lc = val.get("mutability", "MUTABLE"), val.get("has_lifecycle") == "True" stats["mut"][repo_mut] += 1 - if val.get("has_lifecycle") == "False": stats["no_lc"] += 1 + if not has_lc: stats["no_lc"] += 1 - # Tracking per-repo size for the table display - repo_size_accum = 0 - - repo_vulns = Counter() + repo_size_accum, repo_vulns = 0, Counter() images = val.get("images", []) stats["total_images"] += len(images) for img in images: if isinstance(img, list) and len(img) > 0: img = img[0] - - # RESTORED: Storage Aggregation logic img_size = img.get("size_bytes", 0) stats["total_bytes"] += img_size stats["region_bytes"][region] += img_size repo_size_accum += img_size - - # Digest for duplicate checking + digest = img.get("image_digest") if digest: digest_tracker[digest] += 1 - # Security & Aging counts = img.get("severity_counts", {}) if counts: stats["scanned_imgs"] += 1 @@ -103,34 +102,28 @@ def main(): stats["push_count"] += 1 bucket_age(get_days_ago(img.get("last_pulled_at")), stats["pull_ages"]) - print(f"{idx:<4} | {account['account_id']:<15} | {region:<12} | {val['repo_name']:<40} | {repo_size_accum/(1024**3):<10.2f} | {repo_mut:<11} | {'YES' if val.get('has_lifecycle')=='True' else 'NO':<7} | {repo_vulns['CRITICAL']:<8} | {repo_vulns['HIGH']:<8}") + report_print(f"{idx:<4} | {account['account_id']:<15} | {region:<12} | {val['repo_name']:<40} | {repo_size_accum/(1024**3):<10.2f} | {repo_mut:<11} | {'YES' if has_lc else 'NO':<7} | {repo_vulns['CRITICAL']:<8} | {repo_vulns['HIGH']:<8}") - # Aggregated Summary Calculations - total_gb = stats["total_bytes"] / (1024**3) + # Footers avg_img_mb = (stats["total_bytes"] / stats["total_images"]) / (1024**2) if stats["total_images"] > 0 else 0 avg_push_age = stats["total_push_days"] / stats["push_count"] if stats["push_count"] > 0 else 0 - print("-" * report_width) - print(f"ORGANIZATION ECR SUMMARY\n") - print(f" --- Config & Storage ---") - print(f" Repos: {stats['total_repos']} ({stats['mut']['IMMUTABLE']} Immutable / {stats['mut']['MUTABLE']} Mutable) | Missing Lifecycle: {stats['no_lc']}") - print(f" Total Data: {total_gb:.2f} GB | Average Image Size: {avg_img_mb:.2f} MB\n") - - print(f" --- Security & Vulnerabilities ---") - print(f" Scan Coverage: {stats['scanned_imgs']} of {stats['total_images']} images scanned") - print(f" Org Totals: " + " | ".join([f"{s}: {stats['org_vulns'][s]:,}" for s in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]])) + report_print("-" * report_width) + report_print(f"ORGANIZATION ECR SUMMARY\n") + report_print(f" Total Data: {stats['total_bytes']/(1024**3):.2f} GB | Average Image Size: {avg_img_mb:.2f} MB") + report_print(f" Security: " + " | ".join([f"{s}: {stats['org_vulns'][s]:,}" for s in ["CRITICAL", "HIGH", "MEDIUM"]])) - # Duplicate Image Audit + # Duplicate Audit Restored duplicates = {d: c for d, c in digest_tracker.items() if c > 1} - print(f"\n --- Duplicate Image Audit ---") - print(f" Total Unique Digests: {len(digest_tracker)}") - print(f" Total Duplicated Image Instances: {sum(duplicates.values())} (across {len(duplicates)} unique digests)") + report_print(f"\n --- Duplicate Image Audit ---") + report_print(f" Total Duplicated Instances: {sum(duplicates.values())} across {len(duplicates)} unique digests") + if duplicates: + report_print(f" {'Digest':<75} | {'Instance Count'}") + for d, c in sorted(duplicates.items(), key=lambda x: x[1], reverse=True): + report_print(f" {d:<75} | {c}") - print(f"\n --- Age Distribution (Days since Push vs Pull) ---") - print(f" Average Image Age: {avg_push_age:.1f} days") - print(f" Bucket | Pushed Count | Pulled Count") - for b in ['<30', '30-59', '60-89', '90-179', '180-364', '365+']: - print(f" {b:<11} | {stats['push_ages'][b]:<12} | {stats['pull_ages'][b]}") - print("-" * report_width) + report_print("-" * report_width) + report_file.close() + print(f"\nReport saved to: {output_file}") if __name__ == "__main__": main()