diff --git a/local-app/python-tools/cross-organization/check_s3_buckets.py b/local-app/python-tools/cross-organization/check_s3_buckets.py index 57e5d824..aa1c9e30 100755 --- a/local-app/python-tools/cross-organization/check_s3_buckets.py +++ b/local-app/python-tools/cross-organization/check_s3_buckets.py @@ -4,21 +4,15 @@ from datetime import datetime, timedelta # --- VERSIONING --- -__version__ = "1.0.0" +__version__ = "1.1.0" def get_bucket_cloudwatch_metrics(session, region, bucket_name): - """Retrieves the latest daily bucket size and object count from CloudWatch to avoid slow listings.""" + """Retrieves the latest daily bucket size and object count from CloudWatch.""" cw = session.client("cloudwatch", region_name=region) end_time = datetime.utcnow() - start_time = end_time - timedelta(days=2) # 2-day window to guarantee catching the daily push + start_time = end_time - timedelta(days=2) - metrics = { - "size_bytes": 0, - "object_count": 0, - "storage_types": {} - } - - # Storage types to scan + metrics = {"size_bytes": 0, "object_count": 0, "storage_types": {}} s_classes = ["StandardStorage", "IntelligentTieringStorage", "StandardIASuStorage", "GlacierStorage", "DeepArchiveStorage"] for s_class in s_classes: @@ -43,7 +37,6 @@ def get_bucket_cloudwatch_metrics(session, region, bucket_name): except Exception: pass - # Fetch Object Count try: res = cw.get_metric_statistics( Namespace="AWS/S3", @@ -68,8 +61,6 @@ def get_bucket_cloudwatch_metrics(session, region, bucket_name): def analyze_bucket_configuration(session, bucket_name, default_region="us-east-1"): """Queries configuration state policies for an individual bucket.""" s3 = session.client("s3") - - # Determine execution region context mapping try: loc = s3.get_bucket_location(Bucket=bucket_name).get("LocationConstraint") region = loc if loc else default_region @@ -77,80 +68,73 @@ def analyze_bucket_configuration(session, bucket_name, default_region="us-east-1 region = default_region s3_regional = session.client("s3", region_name=region) - bucket_meta = { - "name": bucket_name, - "arn": f"arn:aws:s3:::{bucket_name}", - "region": region, - "versioning_status": "Suspended/Off", - "kms_key_id": "None (SSE-S3)", - "has_lifecycle": "False", - "tags": {}, - "size_bytes": 0, - "object_count": 0, - "storage_types": {} + "name": bucket_name, "arn": f"arn:aws:s3:::{bucket_name}", "region": region, + "versioning_status": "Suspended/Off", "kms_key_id": "None (SSE-S3)", + "has_lifecycle": "False", "tags": {}, "size_bytes": 0, "object_count": 0, "storage_types": {} } - # 1. Versioning try: v_status = s3_regional.get_bucket_versioning(Bucket=bucket_name).get("Status") - if v_status: - bucket_meta["versioning_status"] = v_status - except Exception: - pass + if v_status: bucket_meta["versioning_status"] = v_status + except Exception: pass - # 2. Encryption (KMS Key) try: enc = s3_regional.get_bucket_encryption(Bucket=bucket_name) rules = enc.get("ServerSideEncryptionConfiguration", {}).get("Rules", []) if rules: k_id = rules[0].get("ApplyServerSideEncryptionByDefault", {}).get("KMSMasterKeyId") - if k_id: - bucket_meta["kms_key_id"] = k_id.split("/")[-1] - except Exception: - pass + if k_id: bucket_meta["kms_key_id"] = k_id.split("/")[-1] + except Exception: pass - # 3. Lifecycle Policies try: lc = s3_regional.get_bucket_lifecycle_configuration(Bucket=bucket_name) - if lc.get("Rules"): - bucket_meta["has_lifecycle"] = "True" - except Exception: - pass + if lc.get("Rules"): bucket_meta["has_lifecycle"] = "True" + except Exception: pass - # 4. Tags try: tag_set = s3_regional.get_bucket_tagging(Bucket=bucket_name).get("TagSet", []) bucket_meta["tags"] = {t["Key"]: t["Value"] for t in tag_set} - except Exception: - pass + except Exception: pass - # 5. Inject CloudWatch storage profiles cw_metrics = get_bucket_cloudwatch_metrics(session, region, bucket_name) bucket_meta.update(cw_metrics) - return bucket_meta def account_task(*args, **kwargs): + """Framework entry point hook consumed natively by org_runner3.py""" session = kwargs.get("session") or boto3.Session() acct_meta = kwargs.get("account_metadata_block") or {"account_id": "Unknown", "alias": "Unknown"} + status_callback = kwargs.get("status_callback") account_id = acct_meta.get("account_id", "Unknown") + account_alias = acct_meta.get("alias", "Unknown") s3 = session.client("s3") + # --- FIXED: Explicitly register start state back to org_runner3 tracker UI lane --- + if status_callback: + status_callback(test_label=f"Listing_Buckets", total_count="Evaluating") + buckets_discovered = [] try: all_buckets = s3.list_buckets().get("Buckets", []) - for b in all_buckets: + total_b_count = len(all_buckets) + + for idx, b in enumerate(all_buckets, start=1): + # --- FIXED: Send real-time iterative updates per bucket to preserve active swim lane UI --- + if status_callback: + status_callback(test_label=f"Processing_{idx}_of_{total_b_count}", total_count=str(total_b_count)) + b_name = b["Name"] meta = analyze_bucket_configuration(session, b_name) buckets_discovered.append(meta) + except Exception as e: - return {"account_id": account_id, "alias": acct_meta.get("alias"), "error": str(e)} + return {"account_id": account_id, "alias": account_alias, "error": str(e)} return { "org_id": acct_meta.get("org_id", "N/A"), "account_id": account_id, - "alias": acct_meta.get("alias", "Unknown"), + "alias": account_alias, "data": buckets_discovered } \ No newline at end of file