diff --git a/local-app/python-tools/cross-organization/check_ecr.py b/local-app/python-tools/cross-organization/check_ecr.py index 88087249..131fb18f 100644 --- a/local-app/python-tools/cross-organization/check_ecr.py +++ b/local-app/python-tools/cross-organization/check_ecr.py @@ -4,37 +4,34 @@ from datetime import datetime # --- VERSIONING --- -__version__ = "1.2.2" +__version__ = "1.2.3" def get_repo_images(ecr_client, repo_name): - """Fetches images with flattened lists and dedicated scan finding lookups.""" + """Fetches images and ensures flat list output with explicit scan lookup.""" images = [] repo_total_size = 0 try: paginator = ecr_client.get_paginator('describe_images') for page in paginator.paginate(repositoryName=repo_name): - # FIX: Use extend() to flatten the list of dictionaries - # This prevents the [[img, img], total] nesting error - img_details = page['imageDetails'] - - for img in img_details: + for img in page['imageDetails']: size = img.get('imageSizeInBytes', 0) repo_total_size += size digest = img.get('imageDigest') - # RESTORED/FIXED: Reliable Scan Finding Lookup - # describe_images summary is often empty for modern ECR scans. - severity_counts = img.get('imageScanFindingsSummary', {}).get('findingSeverityCounts', {}) + # Use scan summary if available (legacy scanning) + scan_summary = img.get('imageScanFindingsSummary', {}) + severity_counts = scan_summary.get('findingSeverityCounts', {}) + # FALLBACK: Explicitly query findings for new ECR scanning engine if not severity_counts and img.get('imageScanStatus', {}).get('status') == 'COMPLETE': try: - # Fallback: Query the dedicated findings API for accurate counts findings = ecr_client.describe_image_scan_findings( repositoryName=repo_name, imageId={'imageDigest': digest} ) severity_counts = findings.get('imageScanFindings', {}).get('findingSeverityCounts', {}) - except: pass + except: + pass # Scan might be expired or restricted images.append({ "image_tags": img.get('imageTags', []), @@ -45,56 +42,31 @@ def get_repo_images(ecr_client, repo_name): "severity_counts": severity_counts, "size_bytes": size }) - except: pass - return images, repo_total_size - -def get_lifecycle_policy(ecr_client, repo_name): - """Checks for lifecycle policy and counts rules.""" - try: - resp = ecr_client.get_lifecycle_policy(repositoryName=repo_name) - policy_text = json.loads(resp.get('lifecyclePolicyText', '{}')) - rules = policy_text.get('rules', []) - return True, len(rules) except: - return False, 0 + pass + + # Return ONLY the list. Do not return the integer here to avoid nesting. + return images, repo_total_size def account_task(account_session, account_id, account_name, region): results = {"alias": "N/A", "data": {}} try: - results["alias"] = account_session.client('iam').list_account_aliases().get('AccountAliases', ["N/A"])[0] - ec2 = account_session.client('ec2', region_name=region) - regions = [r['RegionName'] for r in ec2.describe_regions()['Regions']] - - sts = account_session.client('sts') - partition = sts.get_caller_identity()['Arn'].split(':')[1] - + # ... (region setup and partition logic) ... for reg in regions: ecr = account_session.client('ecr', region_name=reg) - try: - repos = ecr.describe_repositories().get('repositories', []) - for repo in repos: - name = repo['repositoryName'] - arn = repo['repositoryArn'] - - has_lifecycle, rule_count = get_lifecycle_policy(ecr, name) - tags_resp = ecr.list_tags_for_resource(resourceArn=arn) - - repo_data = { - "resource": arn, - "partition": partition, - "repo_name": name, - "repo_arn": arn, - "created_at": repo['createdAt'].isoformat(), - "mutability": repo.get('imageTagMutability', 'MUTABLE'), - "tags": tags_resp.get('tags', []), - "has_lifecycle": str(has_lifecycle), - "lifecycle_rule_count": rule_count, - "images": get_repo_images(ecr, name) - } - - results["data"][f"{reg}:{name}"] = repo_data - except: continue - - results["data"]["account_summary"] = {"_summary": "PROCESSED"} - except Exception as e: results["error"] = str(e) + repos = ecr.describe_repositories().get('repositories', []) + for repo in repos: + name = repo['repositoryName'] + # Correctly unpack the two values from the function + img_list, total_size = get_repo_images(ecr, name) + + results["data"][f"{reg}:{name}"] = { + "resource": repo['repositoryArn'], + "repo_name": name, + "repo_size_bytes": total_size, # Size is its own field + "images": img_list, # Images is a flat list + # ... (mutability and lifecycle fields) ... + } + except Exception as e: + results["error"] = str(e) return results