From a5d1de8a62c570af771c99c1c7e37ed62d905319 Mon Sep 17 00:00:00 2001 From: badra001 Date: Tue, 10 Mar 2026 15:03:41 -0400 Subject: [PATCH] widen report --- .../assess_check_scheduling.py | 128 +++++++++++++----- 1 file changed, 92 insertions(+), 36 deletions(-) diff --git a/local-app/python-tools/cross-organization/assess_check_scheduling.py b/local-app/python-tools/cross-organization/assess_check_scheduling.py index fb4402ed..7115b5a1 100755 --- a/local-app/python-tools/cross-organization/assess_check_scheduling.py +++ b/local-app/python-tools/cross-organization/assess_check_scheduling.py @@ -1,34 +1,41 @@ -#!/usr/bin/env python +#!/bin/env python3 + import json, argparse, sys, os, glob from collections import Counter, defaultdict # --- VERSIONING --- -__version__ = "1.3.0" +__version__ = "1.4.0" def find_latest_file(pattern): files = glob.glob(pattern) return max(files, key=os.path.getctime) if files else None def main(): - parser = argparse.ArgumentParser(description="PowerSchedule Assessor - v1.3.0") + parser = argparse.ArgumentParser(description="PowerSchedule Assessor - v1.4.0") parser.add_argument("--input", help="JSON audit file") args = parser.parse_args() input_file = args.input or find_latest_file("audit_results.check_scheduling.*.json") - if not input_file: print("Error: No file found."); sys.exit(1) + if not input_file: + print("Error: No scheduling audit file found."); sys.exit(1) - with open(input_file, 'r') as f: data = json.load(f) + with open(input_file, 'r') as f: + data = json.load(f) + # Core Tracking env_matrix = defaultdict(Counter) env_totals = Counter() - type_matrix = defaultdict(Counter) type_totals = Counter() - # Specific group counters - ec2_groups = Counter({"plain": 0, "asg": 0, "eks": 0}) + # Report 3 Matrix: resource_category -> tag_value -> env -> count + # Categories: "plain_ec2", "asg_ec2", "eks_ec2", "rds" + r3_data = defaultdict(lambda: defaultdict(lambda: defaultdict(int))) + category_env_totals = defaultdict(Counter) + asg_names = set() eks_clusters = set() total_resources = 0 + all_envs = set() for account in data: acc_id = account.get('account_id') @@ -39,43 +46,92 @@ def main(): tags = val.get("tags", {}) res_type = val.get("type", "unknown") - # EKS Cluster Tracking + # Sub-type categorizations + if res_type == "eks_node": cat = "eks_ec2" + elif res_type == "asg_member": cat = "asg_ec2" + elif res_type == "rds": cat = "rds" + else: cat = "plain_ec2" + + # EKS/ASG Metadata if val.get("eks_cluster") and val.get("eks_cluster") != "N/A": eks_clusters.add(f"{acc_id}:{val.get('eks_cluster')}") - - # EC2 Grouping Logic - if res_type in ["plain", "asg_member", "eks_node"]: - if res_type == "eks_node": ec2_groups["eks"] += 1 - elif res_type == "asg_member": ec2_groups["asg"] += 1 - else: ec2_groups["plain"] += 1 - display_type = "ec2" - else: - display_type = res_type - - # Metrics for reports + if val.get("asg_name") and val.get("asg_name") != "N/A": + asg_names.add(f"{acc_id}:{val.get('asg_name')}") + + # Normalization env = tags.get('Environment') or tags.get('environment') or "Undefined" + all_envs.add(env) schedule = tags.get('PowerSchedule', "No Schedule") + sched_lower = schedule.lower().strip() + + # Schedule Enabled Logic + is_enabled = sched_lower not in ["always_on", "no schedule"] + enabled_key = "Scheduled: True" if is_enabled else "Scheduled: False" + + # Populate Matrices env_matrix[env][schedule] += 1 env_totals[env] += 1 - type_matrix[display_type][schedule] += 1 - type_totals[display_type] += 1 + type_totals[cat] += 1 + + r3_data[cat][schedule][env] += 1 + r3_data[cat][enabled_key][env] += 1 + category_env_totals[cat][env] += 1 + + sorted_envs = sorted(list(all_envs)) + report_width = 130 - # ... [Report 1 output logic preserved from v1.2.1] ... + # --- REPORT 1: ENVIRONMENT SUMMARY --- + print(f"\nREPORT 1: BREAKDOWN BY ENVIRONMENT") + print("-" * report_width) + for env in sorted_envs: + print(f"\nEnvironment: {env} (Total: {env_totals[env]})") + for sched, count in sorted(env_matrix[env].items()): + pct = (count / env_totals[env]) * 100 + print(f" {sched:<30} | {count:<5} | {pct:>5.1f}%") + # --- REPORT 2: RESOURCE TYPE SUMMARY --- print(f"\n\nREPORT 2: BREAKDOWN BY RESOURCE TYPE") - print("=" * 40) - for r_type in sorted(type_matrix.keys()): - print(f"\nResource Type: {r_type.upper()} (Total: {type_totals[r_type]})") - if r_type == "ec2": - print(f" -> Plain: {ec2_groups['plain']} | ASG: {ec2_groups['asg']} | EKS: {ec2_groups['eks']}") - # ... [Matrix output logic preserved from v1.2.1] ... - - print("\n" + "=" * 120) + print("-" * report_width) + for cat in sorted(type_totals.keys()): + print(f"Resource Group: {cat.upper():<10} | Total: {type_totals[cat]}") + + # --- REPORT 3: CROSS-ENVIRONMENT MATRIX --- + print(f"\n\nREPORT 3: SCHEDULING MATRIX BY CATEGORY") + for cat in ["plain_ec2", "asg_ec2", "eks_ec2", "rds"]: + cat_total = type_totals[cat] + if cat_total == 0: continue + + print(f"\n{cat.upper()} SCHEDULING DETAIL") + header = f"{'PowerSchedule Tag':<25} | {'Org Total':<12}" + for env in sorted_envs: header += f" | {env[:10]:<12}" + print("-" * len(header)) + print(header) + print("-" * len(header)) + + # Sort tags so 'Scheduled: True/False' are at the bottom + all_tags = sorted([t for t in r3_data[cat].keys() if not t.startswith("Scheduled:")]) + all_tags += ["Scheduled: True", "Scheduled: False"] + + for tag in all_tags: + row_total = sum(r3_data[cat][tag].values()) + row_pct = (row_total / cat_total) * 100 + line = f"{tag[:25]:<25} | {row_total:<4} ({row_pct:>3.0f}%)" + + for env in sorted_envs: + count = r3_data[cat][tag][env] + env_total = category_env_totals[cat][env] + env_pct = (count / env_total * 100) if env_total > 0 else 0 + line += f" | {count:<3} ({env_pct:>3.0f}%)" + print(line) + + # --- ORG SUMMARY --- + print("\n" + "=" * report_width) print(f"ORGANIZATION SUMMARY") - print(f" Total Resources: {total_resources}") - print(f" Total ASGs Identified: {len(asg_names)}") - print(f" Total EKS Clusters: {len(eks_clusters)}") # NEW - print(f" Total EKS Nodes: {ec2_groups['eks']}") # NEW - print("=" * 120) + print(f" Accounts Checked: {len(data)}") + print(f" Total Resources: {total_resources}") + print(f" Total ASGs: {len(asg_names)}") + print(f" Total EKS Clusters: {len(eks_clusters)}") + print(f" Total EKS Nodes: {type_totals['eks_ec2']}") + print("=" * report_width) if __name__ == "__main__": main()