diff --git a/local-app/python-tools/cross-organization/assess_check_s3_buckets.py b/local-app/python-tools/cross-organization/assess_check_s3_buckets.py index 94e3f8bb..81a88e50 100755 --- a/local-app/python-tools/cross-organization/assess_check_s3_buckets.py +++ b/local-app/python-tools/cross-organization/assess_check_s3_buckets.py @@ -9,7 +9,7 @@ from collections import defaultdict # --- VERSIONING --- -__version__ = "1.2.1" +__version__ = "1.2.2" UNIT_CONVERSION_MAP = { "B": {"divisor": 1, "label": "Bytes"}, @@ -24,7 +24,7 @@ def find_latest_file(pattern): return max(files, key=os.path.getctime) if files else None def main(): - parser = argparse.ArgumentParser(description="AWS S3 Fleet Storage Filtered Assessor Suite v1.2.1") + parser = argparse.ArgumentParser(description="AWS S3 Fleet Storage Filtered Assessor Suite v1.2.2") parser.add_argument("--input", help="JSON file (default: latest audit_results.check_s3_buckets.*.json)") parser.add_argument("--size-units", default="GB", choices=["B", "MB", "GB", "TB", "PB"], help="Target size scale unit representation to output in reports (Default: GB)") @@ -45,7 +45,6 @@ def main(): txt_filename = f"{os.path.splitext(input_file)[0]}.txt" csv_filename = f"s3_global_inventory_{timestamp_suffix}.csv" - # Compile the bucket filter regex if provided bucket_regex = None if args.bucket_name: try: @@ -102,7 +101,6 @@ def main(): b["versioning_status"], b["kms_key_id"], b["has_lifecycle"] ]) - # Write Filtered Inventory CSV File with open(csv_filename, "w", newline="", encoding="utf-8") as cf: writer = csv.writer(cf) writer.writerow(["Account ID", "Account Alias", "Bucket Name", "Region", "ARN", f"Size ({target_unit})", "Object Count", "Versioning Status", "KMS Key ID", "Has Lifecycle"]) @@ -110,7 +108,6 @@ def main(): scaled_csv_size = r[5] if target_unit == "B" else round(r[5] / divisor, 4) writer.writerow([r[0], r[1], r[2], r[3], r[4], scaled_csv_size, r[6], r[7], r[8], r[9]]) - # Compile Aligned TXT Dashboard Report Asset with open(txt_filename, "w", encoding="utf-8") as tf: tf.write("=" * 155 + "\n") tf.write(f"AWS ORGANIZATIONAL S3 METRICS STORAGE OPTIMIZATION REPORT | Source: {input_base_name}\n") @@ -127,7 +124,7 @@ def main(): tf.write(f" ▶ Total Global Footprint Aggregation : {scaled_global_size:{fmt_spec}} {unit_label}\n") tf.write(f" ▶ Total Allocated Object Count : {total_global_objects:,} Objects\n\n") - # --- FIXED: Explicitly isolate clean format specifiers upfront to avoid syntax tuples --- + # Section 2 tf.write("[SECTION 2: ACCOUNT BILLING SECTOR SUMMARY BREAKDOWN]\n") if target_unit == "B": fmt_acct = f" • Target Account: {{:<45}} | Buckets: {{:>4}} | Size ({target_unit}): {{:>15,d}} | Objects: {{:>14,}}\n" @@ -139,7 +136,7 @@ def main(): tf.write(fmt_acct.format(acct, metrics["count"], scaled_acct_size, metrics["objects"])) tf.write("\n") - # --- FIXED: Isolate clean format specifiers for Section 3 --- + # Section 3 tf.write("[SECTION 3: SPACE DISTRIBUTION BY STORAGE TYPE TIER]\n") if target_unit == "B": fmt_tier = f" • Storage Tier: {{:<30}} | Footprint ({target_unit}): {{:>17,d}} | Share %: {{:>8.2f}}\n" @@ -152,21 +149,21 @@ def main(): tf.write(fmt_tier.format(s_tier, scaled_tier_size, share)) tf.write("\n") - # Section 4: Granular Matrix Table + # --- FIXED: Use clean pre-compiled f-string masks to inject tracking widths safely --- tf.write("[SECTION 4: GRANULAR BUCKET METADATA INVENTORY MATRIX]\n") size_header_str = f"Size ({target_unit})" - fmt_row_header = f" {{:<12}} | {{:<{{max_alias_w}}}} | {{:<{{max_bucket_w}}}} | {{:<12}} | {{:>18}} | {{:>12}} | {{:<10}} | {{:<13}}\n" - fmt_row_data = f" {{:<12}} | {{:<{{max_alias_w}}}} | {{:<{{max_bucket_w}}}} | {{:<12}} | {{:>18{',d' if target_unit == 'B' else ',.2f'}}} | {{:>12,}} | {{:<10}} | {{:<13}}\n" + fmt_row_header = f" {{:<12}} | {{:<{max_alias_w}}} | {{:<{max_bucket_w}}} | {{:<12}} | {{:>18}} | {{:>12}} | {{:<10}} | {{:<13}}\n" - f_header = fmt_row_header.format("Account ID", size_header_str, "Version", "Lifecycle", max_alias_w=max_alias_w, max_bucket_w=max_bucket_w) - tf.write(f_header.format("Account ID", "Account Alias", "Bucket Name", "Region", size_header_str, "Objects", "Version", "Lifecycle")) + size_data_spec = ",d" if target_unit == "B" else ",.2f" + fmt_row_data = f" {{:<12}} | {{:<{max_alias_w}}} | {{:<{max_bucket_w}}} | {{:<12}} | {{:>18{size_data_spec}}} | {{:>12,}} | {{:<10}} | {{:<13}}\n" + tf.write(fmt_row_header.format("Account ID", "Account Alias", "Bucket Name", "Region", size_header_str, "Objects", "Version", "Lifecycle")) tf.write(" " + "-" * (max_alias_w + max_bucket_w + 100) + "\n") + for r in flat_rows: scaled_bucket_size = r[5] if target_unit == "B" else (r[5] / divisor) - f_data = fmt_row_data.format(max_alias_w=max_alias_w, max_bucket_w=max_bucket_w) - tf.write(f_data.format(r[0], r[1], r[2], r[3], scaled_bucket_size, r[6], r[7], r[9])) + tf.write(fmt_row_data.format(r[0], r[1], r[2], r[3], scaled_bucket_size, r[6], r[7], r[9])) print(f"[+] Output unit normalization target mapped successfully to: {target_unit}") print(f"[+] Multi-Account Analytical Dashboard Saved: {txt_filename}")