runbooks 0.9.9__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/cloud_foundations_assessment.py +626 -0
  6. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  7. runbooks/cfat/weight_config.ts +574 -0
  8. runbooks/cloudops/cost_optimizer.py +95 -33
  9. runbooks/common/__init__.py +26 -9
  10. runbooks/common/aws_pricing.py +1353 -0
  11. runbooks/common/aws_pricing_api.py +205 -0
  12. runbooks/common/aws_utils.py +2 -2
  13. runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
  14. runbooks/common/cross_account_manager.py +606 -0
  15. runbooks/common/date_utils.py +115 -0
  16. runbooks/common/enhanced_exception_handler.py +14 -7
  17. runbooks/common/env_utils.py +96 -0
  18. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  19. runbooks/common/mcp_integration.py +49 -2
  20. runbooks/common/organizations_client.py +579 -0
  21. runbooks/common/profile_utils.py +127 -72
  22. runbooks/common/rich_utils.py +3 -3
  23. runbooks/finops/cost_optimizer.py +2 -1
  24. runbooks/finops/dashboard_runner.py +47 -28
  25. runbooks/finops/ebs_optimizer.py +56 -9
  26. runbooks/finops/elastic_ip_optimizer.py +13 -9
  27. runbooks/finops/embedded_mcp_validator.py +31 -0
  28. runbooks/finops/enhanced_trend_visualization.py +10 -4
  29. runbooks/finops/finops_dashboard.py +6 -5
  30. runbooks/finops/iam_guidance.py +6 -1
  31. runbooks/finops/markdown_exporter.py +217 -2
  32. runbooks/finops/nat_gateway_optimizer.py +76 -20
  33. runbooks/finops/tests/test_integration.py +3 -1
  34. runbooks/finops/vpc_cleanup_exporter.py +28 -26
  35. runbooks/finops/vpc_cleanup_optimizer.py +363 -16
  36. runbooks/inventory/__init__.py +10 -1
  37. runbooks/inventory/cloud_foundations_integration.py +409 -0
  38. runbooks/inventory/core/collector.py +1177 -94
  39. runbooks/inventory/discovery.md +339 -0
  40. runbooks/inventory/drift_detection_cli.py +327 -0
  41. runbooks/inventory/inventory_mcp_cli.py +171 -0
  42. runbooks/inventory/inventory_modules.py +6 -9
  43. runbooks/inventory/list_ec2_instances.py +3 -3
  44. runbooks/inventory/mcp_inventory_validator.py +2149 -0
  45. runbooks/inventory/mcp_vpc_validator.py +23 -6
  46. runbooks/inventory/organizations_discovery.py +104 -9
  47. runbooks/inventory/rich_inventory_display.py +129 -1
  48. runbooks/inventory/unified_validation_engine.py +1279 -0
  49. runbooks/inventory/verify_ec2_security_groups.py +3 -1
  50. runbooks/inventory/vpc_analyzer.py +825 -7
  51. runbooks/inventory/vpc_flow_analyzer.py +36 -42
  52. runbooks/main.py +708 -47
  53. runbooks/monitoring/performance_monitor.py +11 -7
  54. runbooks/operate/base.py +9 -6
  55. runbooks/operate/deployment_framework.py +5 -4
  56. runbooks/operate/deployment_validator.py +6 -5
  57. runbooks/operate/dynamodb_operations.py +6 -5
  58. runbooks/operate/ec2_operations.py +3 -2
  59. runbooks/operate/mcp_integration.py +6 -5
  60. runbooks/operate/networking_cost_heatmap.py +21 -16
  61. runbooks/operate/s3_operations.py +13 -12
  62. runbooks/operate/vpc_operations.py +100 -12
  63. runbooks/remediation/base.py +4 -2
  64. runbooks/remediation/commons.py +5 -5
  65. runbooks/remediation/commvault_ec2_analysis.py +68 -15
  66. runbooks/remediation/config/accounts_example.json +31 -0
  67. runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
  68. runbooks/remediation/multi_account.py +120 -7
  69. runbooks/remediation/rds_snapshot_list.py +5 -3
  70. runbooks/remediation/remediation_cli.py +710 -0
  71. runbooks/remediation/universal_account_discovery.py +377 -0
  72. runbooks/security/compliance_automation_engine.py +99 -20
  73. runbooks/security/config/__init__.py +24 -0
  74. runbooks/security/config/compliance_config.py +255 -0
  75. runbooks/security/config/compliance_weights_example.json +22 -0
  76. runbooks/security/config_template_generator.py +500 -0
  77. runbooks/security/security_cli.py +377 -0
  78. runbooks/validation/__init__.py +21 -1
  79. runbooks/validation/cli.py +8 -7
  80. runbooks/validation/comprehensive_2way_validator.py +2007 -0
  81. runbooks/validation/mcp_validator.py +965 -101
  82. runbooks/validation/terraform_citations_validator.py +363 -0
  83. runbooks/validation/terraform_drift_detector.py +1098 -0
  84. runbooks/vpc/cleanup_wrapper.py +231 -10
  85. runbooks/vpc/config.py +346 -73
  86. runbooks/vpc/cross_account_session.py +312 -0
  87. runbooks/vpc/heatmap_engine.py +115 -41
  88. runbooks/vpc/manager_interface.py +9 -9
  89. runbooks/vpc/mcp_no_eni_validator.py +1630 -0
  90. runbooks/vpc/networking_wrapper.py +14 -8
  91. runbooks/vpc/runbooks_adapter.py +33 -12
  92. runbooks/vpc/tests/conftest.py +4 -2
  93. runbooks/vpc/tests/test_cost_engine.py +4 -2
  94. runbooks/vpc/unified_scenarios.py +73 -3
  95. runbooks/vpc/vpc_cleanup_integration.py +512 -78
  96. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/METADATA +94 -52
  97. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/RECORD +101 -81
  98. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  99. runbooks/finops/runbooks.security.report_generator.log +0 -0
  100. runbooks/finops/runbooks.security.run_script.log +0 -0
  101. runbooks/finops/runbooks.security.security_export.log +0 -0
  102. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  103. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  104. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  105. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  106. runbooks/inventory/runbooks.security.run_script.log +0 -0
  107. runbooks/inventory/runbooks.security.security_export.log +0 -0
  108. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
  109. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
  111. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/top_level.txt +0 -0
@@ -47,9 +47,11 @@ from ..common.rich_utils import (
47
47
  console, print_header, print_success, print_error, print_warning, print_info,
48
48
  create_table, create_progress_bar, format_cost, create_panel, STATUS_INDICATORS
49
49
  )
50
+ from ..common.aws_pricing import DynamicAWSPricing
50
51
  from .embedded_mcp_validator import EmbeddedMCPValidator
51
52
  from ..common.profile_utils import get_profile_for_operation
52
53
  from ..security.enterprise_security_framework import EnterpriseSecurityFramework
54
+ from ..vpc.mcp_no_eni_validator import NOENIVPCMCPValidator, DynamicDiscoveryResults
53
55
 
54
56
  logger = logging.getLogger(__name__)
55
57
 
@@ -364,22 +366,76 @@ class VPCCleanupOptimizer:
364
366
  """
365
367
  Enhanced VPC discovery with organization account context.
366
368
 
367
- CRITICAL INSIGHT: This method discovers VPCs that the current profile can access,
368
- which may include VPCs from multiple accounts if the profile has cross-account permissions
369
- (e.g., via AWS SSO or cross-account roles).
369
+ CRITICAL FIX: This method now attempts to discover VPCs across multiple accounts
370
+ by trying different access patterns:
371
+ 1. Direct access with current profile
372
+ 2. Cross-account role assumption (if available)
373
+ 3. Aggregation from multiple AWS SSO profiles
370
374
  """
371
375
  vpc_candidates = []
376
+ total_accounts_checked = 0
377
+ accounts_with_vpcs = set()
372
378
 
373
379
  if progress_callback:
374
- progress_callback("Discovering VPCs across regions...")
380
+ progress_callback(f"Discovering VPCs across {len(account_ids)} organization accounts...")
375
381
 
376
382
  # Get list of all regions
377
383
  ec2_client = self.session.client('ec2', region_name='us-east-1')
378
384
  regions = [region['RegionName'] for region in ec2_client.describe_regions()['Regions']]
379
385
 
380
- print_info(f"🌍 Scanning {len(regions)} AWS regions for accessible VPCs...")
386
+ print_info(f"🌍 Scanning {len(regions)} AWS regions across {len(account_ids)} accounts...")
381
387
 
388
+ # First, discover VPCs in current profile's accessible accounts
389
+ current_account_vpcs = self._discover_vpcs_current_profile(regions, progress_callback)
390
+ vpc_candidates.extend(current_account_vpcs)
391
+
392
+ # Extract unique account IDs from discovered VPCs
393
+ for vpc in current_account_vpcs:
394
+ if vpc.account_id:
395
+ accounts_with_vpcs.add(vpc.account_id)
396
+
397
+ # Attempt cross-account discovery for remaining accounts
398
+ remaining_accounts = [acc for acc in account_ids if acc not in accounts_with_vpcs]
399
+
400
+ if remaining_accounts:
401
+ print_info(f"🔄 Attempting cross-account discovery for {len(remaining_accounts)} additional accounts...")
402
+
403
+ # Try different access patterns for remaining accounts
404
+ for account_id in remaining_accounts[:10]: # Limit to first 10 for performance
405
+ total_accounts_checked += 1
406
+ account_name = accounts_info.get(account_id, {}).get('name', 'Unknown')
407
+
408
+ if progress_callback:
409
+ progress_callback(f"Checking account {account_name} ({account_id[:12]}...)")
410
+
411
+ # Attempt cross-account access
412
+ cross_account_vpcs = self._attempt_cross_account_discovery(
413
+ account_id, account_name, regions
414
+ )
415
+
416
+ if cross_account_vpcs:
417
+ vpc_candidates.extend(cross_account_vpcs)
418
+ accounts_with_vpcs.add(account_id)
419
+ print_success(f" ✅ Found {len(cross_account_vpcs)} VPCs in {account_name}")
420
+
421
+ # Summary
422
+ print_success(f"✅ Discovered {len(vpc_candidates)} total VPCs across {len(accounts_with_vpcs)} accounts")
423
+ print_info(f"📊 Organization scope: {len(account_ids)} accounts, {total_accounts_checked} checked, {len(accounts_with_vpcs)} with VPCs")
424
+
425
+ # If we still have < 13 VPCs, provide guidance
426
+ if len(vpc_candidates) < 13:
427
+ print_warning(f"⚠️ Only {len(vpc_candidates)} VPCs found (target: ≥13). Consider:")
428
+ print_info(" 1. Using MANAGEMENT_PROFILE with broader cross-account access")
429
+ print_info(" 2. Configuring cross-account roles for VPC discovery")
430
+ print_info(" 3. Running discovery from each account individually")
431
+
432
+ return vpc_candidates
433
+
434
+ def _discover_vpcs_current_profile(self, regions: List[str], progress_callback=None) -> List[VPCCleanupCandidate]:
435
+ """Discover VPCs accessible with current profile."""
436
+ vpc_candidates = []
382
437
  regions_with_vpcs = 0
438
+
383
439
  for region in regions:
384
440
  try:
385
441
  regional_ec2 = self.session.client('ec2', region_name=region)
@@ -409,13 +465,64 @@ class VPCCleanupOptimizer:
409
465
  regions_with_vpcs += 1
410
466
 
411
467
  except ClientError as e:
412
- if "UnauthorizedOperation" in str(e):
413
- print_warning(f"No access to region {region} (expected for cross-account)")
414
- else:
415
- print_warning(f"Could not access region {region}: {e}")
468
+ # Silently skip regions with no access
469
+ pass
416
470
 
417
- print_success(f"✅ Discovered {len(vpc_candidates)} VPCs across {regions_with_vpcs} accessible regions")
418
- print_info(f"📊 Organization context: {len(account_ids)} accounts in scope")
471
+ return vpc_candidates
472
+
473
+ def _attempt_cross_account_discovery(self, account_id: str, account_name: str,
474
+ regions: List[str]) -> List[VPCCleanupCandidate]:
475
+ """Attempt to discover VPCs in a specific account using cross-account access."""
476
+ vpc_candidates = []
477
+
478
+ # Try to assume a cross-account role (if configured)
479
+ role_name = "OrganizationAccountAccessRole" # Standard AWS Organizations role
480
+
481
+ try:
482
+ # Attempt to assume role in target account
483
+ sts_client = self.session.client('sts')
484
+ assumed_role = sts_client.assume_role(
485
+ RoleArn=f"arn:aws:iam::{account_id}:role/{role_name}",
486
+ RoleSessionName=f"VPCDiscovery-{account_id[:12]}"
487
+ )
488
+
489
+ # Create session with assumed role credentials
490
+ assumed_session = boto3.Session(
491
+ aws_access_key_id=assumed_role['Credentials']['AccessKeyId'],
492
+ aws_secret_access_key=assumed_role['Credentials']['SecretAccessKey'],
493
+ aws_session_token=assumed_role['Credentials']['SessionToken']
494
+ )
495
+
496
+ # Discover VPCs in target account
497
+ for region in regions[:3]: # Check first 3 regions for performance
498
+ try:
499
+ ec2_client = assumed_session.client('ec2', region_name=region)
500
+ response = ec2_client.describe_vpcs()
501
+
502
+ for vpc in response['Vpcs']:
503
+ candidate = VPCCleanupCandidate(
504
+ vpc_id=vpc['VpcId'],
505
+ region=region,
506
+ state=vpc['State'],
507
+ cidr_block=vpc['CidrBlock'],
508
+ is_default=vpc.get('IsDefault', False),
509
+ account_id=account_id, # Set explicit account ID
510
+ dependency_analysis=VPCDependencyAnalysis(
511
+ vpc_id=vpc['VpcId'],
512
+ region=region,
513
+ is_default_vpc=vpc.get('IsDefault', False)
514
+ ),
515
+ tags={tag['Key']: tag['Value'] for tag in vpc.get('Tags', [])}
516
+ )
517
+ vpc_candidates.append(candidate)
518
+
519
+ except Exception:
520
+ # Skip regions with access issues
521
+ pass
522
+
523
+ except Exception as e:
524
+ # Cross-account access not available - this is expected for most profiles
525
+ pass
419
526
 
420
527
  return vpc_candidates
421
528
 
@@ -482,6 +589,237 @@ class VPCCleanupOptimizer:
482
589
  print_success(f"✅ Discovered {len(vpc_candidates)} VPC candidates across {len(regions)} regions")
483
590
  return vpc_candidates
484
591
 
592
+ async def discover_no_eni_vpcs_with_mcp_validation(self,
593
+ target_regions: List[str] = None,
594
+ max_concurrent_accounts: int = 10) -> Tuple[List[VPCCleanupCandidate], DynamicDiscoveryResults]:
595
+ """
596
+ Discover NO-ENI VPCs across all AWS accounts using real-time MCP validation.
597
+
598
+ This method integrates with the dynamic MCP validator to discover the actual
599
+ count of NO-ENI VPCs across all accessible accounts, not hardcoded numbers.
600
+
601
+ Args:
602
+ target_regions: List of regions to scan (default: ['ap-southeast-2'])
603
+ max_concurrent_accounts: Maximum concurrent account scans
604
+
605
+ Returns:
606
+ Tuple of (VPC cleanup candidates, Dynamic discovery results)
607
+ """
608
+ if target_regions is None:
609
+ target_regions = ['ap-southeast-2']
610
+
611
+ print_header("🌐 Real-Time NO-ENI VPC Discovery", "MCP-Validated VPC Cleanup Analysis")
612
+
613
+ # Initialize MCP validator with universal profile support
614
+ print_info("🔧 Initializing dynamic MCP validator...")
615
+ mcp_validator = NOENIVPCMCPValidator(user_profile=self.profile)
616
+
617
+ # Perform dynamic discovery across all accounts
618
+ print_info("🚀 Starting real-time discovery across all AWS accounts...")
619
+ discovery_results = await mcp_validator.discover_all_no_eni_vpcs_dynamically(
620
+ target_regions=target_regions,
621
+ max_concurrent_accounts=max_concurrent_accounts
622
+ )
623
+
624
+ # Convert MCP discovery results to VPC cleanup candidates
625
+ print_info("🔄 Converting MCP results to VPC cleanup candidates...")
626
+ cleanup_candidates = []
627
+
628
+ for target in discovery_results.account_region_results:
629
+ if not target.has_access or not target.no_eni_vpcs:
630
+ continue
631
+
632
+ # Get detailed VPC information for each NO-ENI VPC
633
+ try:
634
+ # Use appropriate session for this account
635
+ session = self._get_session_for_account(target.account_id)
636
+ ec2_client = session.client('ec2', region_name=target.region)
637
+
638
+ # Get VPC details
639
+ vpc_response = ec2_client.describe_vpcs(VpcIds=target.no_eni_vpcs)
640
+
641
+ for vpc in vpc_response.get('Vpcs', []):
642
+ # Create comprehensive dependency analysis
643
+ dependency_analysis = await self._create_dependency_analysis_for_vpc(
644
+ vpc['VpcId'], target.region, ec2_client
645
+ )
646
+
647
+ # Create VPC cleanup candidate
648
+ candidate = VPCCleanupCandidate(
649
+ vpc_id=vpc['VpcId'],
650
+ region=target.region,
651
+ state=vpc.get('State', 'unknown'),
652
+ cidr_block=vpc.get('CidrBlock', ''),
653
+ is_default=vpc.get('IsDefault', False),
654
+ dependency_analysis=dependency_analysis,
655
+ cleanup_bucket='internal', # NO-ENI VPCs go to internal bucket
656
+ monthly_cost=self._estimate_vpc_monthly_cost(vpc),
657
+ annual_savings=self._estimate_vpc_monthly_cost(vpc) * 12,
658
+ cleanup_recommendation='ready' if dependency_analysis.eni_count == 0 else 'investigate',
659
+ risk_assessment='low' if dependency_analysis.eni_count == 0 else 'medium',
660
+ business_impact='minimal',
661
+ tags=self._extract_vpc_tags(vpc),
662
+ account_id=target.account_id,
663
+ flow_logs_enabled=await self._check_flow_logs_enabled(vpc['VpcId'], ec2_client)
664
+ )
665
+
666
+ cleanup_candidates.append(candidate)
667
+
668
+ except Exception as e:
669
+ print_warning(f"Failed to analyze VPC details for account {target.account_id}: {e}")
670
+ continue
671
+
672
+ # Display integration results
673
+ print_header("🎯 MCP-Validated VPC Cleanup Summary", "Real-Time Integration Results")
674
+ console.print(f"[bold green]✅ NO-ENI VPCs discovered: {len(cleanup_candidates)}[/bold green]")
675
+ console.print(f"[bold blue]📊 Accounts scanned: {discovery_results.total_accounts_scanned}[/bold blue]")
676
+ console.print(f"[bold yellow]🌍 Regions scanned: {discovery_results.total_regions_scanned}[/bold yellow]")
677
+ console.print(f"[bold magenta]🧪 MCP validation accuracy: {discovery_results.mcp_validation_accuracy:.2f}%[/bold magenta]")
678
+
679
+ # Calculate potential savings
680
+ total_annual_savings = sum(candidate.annual_savings for candidate in cleanup_candidates)
681
+ console.print(f"[bold cyan]💰 Potential annual savings: {format_cost(total_annual_savings)}[/bold cyan]")
682
+
683
+ # Validation status
684
+ if discovery_results.mcp_validation_accuracy >= 99.5:
685
+ print_success(f"✅ ENTERPRISE VALIDATION PASSED: {discovery_results.mcp_validation_accuracy:.2f}% accuracy")
686
+ else:
687
+ print_warning(f"⚠️ VALIDATION REVIEW REQUIRED: {discovery_results.mcp_validation_accuracy:.2f}% accuracy")
688
+
689
+ return cleanup_candidates, discovery_results
690
+
691
+ def _determine_profile_type(self, profile_name: str) -> Optional[str]:
692
+ """Determine profile type from profile name for universal compatibility."""
693
+ # Universal pattern matching for any AWS profile naming convention
694
+ if 'billing' in profile_name.lower() or 'Billing' in profile_name:
695
+ return 'BILLING'
696
+ elif 'management' in profile_name.lower() or 'admin' in profile_name.lower():
697
+ return 'MANAGEMENT'
698
+ elif 'ops' in profile_name.lower() or 'operational' in profile_name.lower():
699
+ return 'CENTRALISED_OPS'
700
+ return None
701
+
702
+ def _get_session_for_account(self, account_id: str) -> boto3.Session:
703
+ """Get appropriate session for accessing a specific account using universal profile management."""
704
+ from runbooks.common.profile_utils import get_profile_for_operation
705
+
706
+ # In enterprise setup, would assume role here
707
+ # For now, return session with best available profile using three-tier priority system
708
+
709
+ # Try different operation types in priority order
710
+ profile_types = ['management', 'operational', 'billing']
711
+
712
+ for profile_type in profile_types:
713
+ try:
714
+ profile_name = get_profile_for_operation(profile_type, self.profile)
715
+ session = boto3.Session(profile_name=profile_name)
716
+ # Verify access
717
+ sts_client = session.client('sts')
718
+ identity = sts_client.get_caller_identity()
719
+
720
+ if identity['Account'] == account_id:
721
+ return session
722
+ except Exception:
723
+ continue
724
+
725
+ # Fallback to current session
726
+ return self.session
727
+
728
+ async def _create_dependency_analysis_for_vpc(self,
729
+ vpc_id: str,
730
+ region: str,
731
+ ec2_client) -> VPCDependencyAnalysis:
732
+ """Create comprehensive dependency analysis for a VPC."""
733
+ try:
734
+ # Get ENI count
735
+ eni_response = ec2_client.describe_network_interfaces(
736
+ Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
737
+ )
738
+ eni_count = len(eni_response.get('NetworkInterfaces', []))
739
+
740
+ # Get route tables
741
+ rt_response = ec2_client.describe_route_tables(
742
+ Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
743
+ )
744
+ route_tables = [rt['RouteTableId'] for rt in rt_response.get('RouteTables', [])]
745
+
746
+ # Get security groups
747
+ sg_response = ec2_client.describe_security_groups(
748
+ Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
749
+ )
750
+ security_groups = [sg['GroupId'] for sg in sg_response.get('SecurityGroups', [])]
751
+
752
+ # Get internet gateways
753
+ igw_response = ec2_client.describe_internet_gateways(
754
+ Filters=[{'Name': 'attachment.vpc-id', 'Values': [vpc_id]}]
755
+ )
756
+ internet_gateways = [igw['InternetGatewayId'] for igw in igw_response.get('InternetGateways', [])]
757
+
758
+ # Get NAT gateways
759
+ nat_response = ec2_client.describe_nat_gateways(
760
+ Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
761
+ )
762
+ nat_gateways = [nat['NatGatewayId'] for nat in nat_response.get('NatGateways', [])]
763
+
764
+ # Determine risk level
765
+ if eni_count == 0 and len(nat_gateways) == 0 and len(internet_gateways) <= 1:
766
+ risk_level = 'low'
767
+ elif eni_count == 0:
768
+ risk_level = 'medium'
769
+ else:
770
+ risk_level = 'high'
771
+
772
+ return VPCDependencyAnalysis(
773
+ vpc_id=vpc_id,
774
+ region=region,
775
+ eni_count=eni_count,
776
+ route_tables=route_tables,
777
+ security_groups=security_groups,
778
+ internet_gateways=internet_gateways,
779
+ nat_gateways=nat_gateways,
780
+ dependency_risk_level=risk_level
781
+ )
782
+
783
+ except Exception as e:
784
+ print_warning(f"Failed to analyze dependencies for {vpc_id}: {e}")
785
+ return VPCDependencyAnalysis(
786
+ vpc_id=vpc_id,
787
+ region=region,
788
+ dependency_risk_level='unknown'
789
+ )
790
+
791
+ def _estimate_vpc_monthly_cost(self, vpc: Dict[str, Any]) -> float:
792
+ """Estimate monthly cost for VPC resources."""
793
+ # Base VPC cost estimation (simplified)
794
+ # In enterprise setup, would integrate with Cost Explorer
795
+ base_cost = 0.0
796
+
797
+ # Default VPCs might have default resources
798
+ if vpc.get('IsDefault', False):
799
+ base_cost += 5.0 # Estimated monthly cost for default VPC resources
800
+
801
+ return base_cost
802
+
803
+ def _extract_vpc_tags(self, vpc: Dict[str, Any]) -> Dict[str, str]:
804
+ """Extract tags from VPC data."""
805
+ tags = {}
806
+ for tag in vpc.get('Tags', []):
807
+ tags[tag['Key']] = tag['Value']
808
+ return tags
809
+
810
+ async def _check_flow_logs_enabled(self, vpc_id: str, ec2_client) -> bool:
811
+ """Check if VPC Flow Logs are enabled."""
812
+ try:
813
+ response = ec2_client.describe_flow_logs(
814
+ Filters=[
815
+ {'Name': 'resource-id', 'Values': [vpc_id]},
816
+ {'Name': 'resource-type', 'Values': ['VPC']}
817
+ ]
818
+ )
819
+ return len(response.get('FlowLogs', [])) > 0
820
+ except Exception:
821
+ return False
822
+
485
823
  def _analyze_vpc_dependencies(self, candidates: List[VPCCleanupCandidate]) -> List[VPCCleanupCandidate]:
486
824
  """Analyze VPC dependencies for cleanup safety assessment."""
487
825
  print_info("🔍 Analyzing VPC dependencies for safety assessment...")
@@ -972,12 +1310,21 @@ class VPCCleanupOptimizer:
972
1310
  """Calculate VPC cleanup costs and savings estimation."""
973
1311
  print_info("💰 Calculating VPC cleanup costs and savings...")
974
1312
 
975
- # Standard VPC cost estimation (based on AWSO-05 analysis)
976
- # These are conservative estimates for VPC resources
1313
+ # Dynamic VPC cost calculation (enterprise compliance)
1314
+ # Using dynamic pricing engine for accurate regional costs
1315
+ pricing_engine = DynamicAWSPricing()
1316
+ default_region = 'us-east-1' # Default region for cost estimation
1317
+
977
1318
  monthly_vpc_base_cost = 0.0 # VPCs themselves are free
978
- monthly_nat_gateway_cost = 45.0 # $45/month per NAT Gateway
979
- monthly_vpc_endpoint_cost = 7.2 # $7.20/month per VPC Endpoint
980
- monthly_data_processing_cost = 50.0 # Estimated data processing costs
1319
+ nat_result = pricing_engine.get_service_pricing('nat_gateway', default_region)
1320
+ monthly_nat_gateway_cost = nat_result.monthly_cost
1321
+
1322
+ endpoint_result = pricing_engine.get_service_pricing('vpc_endpoint', default_region)
1323
+ monthly_vpc_endpoint_cost = endpoint_result.monthly_cost
1324
+
1325
+ # Data processing costs vary by usage - using conservative estimate per region
1326
+ regional_multiplier = pricing_engine.regional_multipliers.get(default_region, 1.0)
1327
+ monthly_data_processing_cost = 50.0 * regional_multiplier # Base estimate adjusted for region
981
1328
 
982
1329
  total_annual_savings = 0.0
983
1330
  cost_details = {}
@@ -26,6 +26,11 @@ from runbooks.inventory.collectors.base import BaseResourceCollector
26
26
  from runbooks.inventory.core.collector import InventoryCollector
27
27
  from runbooks.inventory.core.formatter import InventoryFormatter
28
28
 
29
+ # Enhanced collector integrated into core collector module
30
+ from runbooks.inventory.core.collector import (
31
+ EnhancedInventoryCollector,
32
+ )
33
+
29
34
  # Data models
30
35
  from runbooks.inventory.models.account import AWSAccount, OrganizationAccount
31
36
  from runbooks.inventory.models.inventory import InventoryMetadata, InventoryResult
@@ -38,13 +43,17 @@ from runbooks.inventory.utils.validation import validate_aws_account_id, validat
38
43
  # VPC Module Migration Integration
39
44
  from runbooks.inventory.vpc_analyzer import VPCAnalyzer, VPCDiscoveryResult, AWSOAnalysis
40
45
 
46
+ # Note: EnhancedInventoryCollector now imported above from core.collector
47
+
41
48
  # Import centralized version from main runbooks package
42
49
  from runbooks import __version__
43
50
 
44
51
  __all__ = [
45
52
  # Core functionality
46
53
  "InventoryCollector",
47
- "InventoryFormatter",
54
+ "InventoryFormatter",
55
+ # Enhanced functionality with proven finops patterns
56
+ "EnhancedInventoryCollector",
48
57
  # Base classes for extension
49
58
  "BaseResourceCollector",
50
59
  # Data models