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.
- runbooks/__init__.py +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
- runbooks/cfat/app.ts +27 -19
- runbooks/cfat/assessment/runner.py +6 -5
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cfat/tests/test_weight_configuration.ts +449 -0
- runbooks/cfat/weight_config.ts +574 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/__init__.py +26 -9
- runbooks/common/aws_pricing.py +1353 -0
- runbooks/common/aws_pricing_api.py +205 -0
- runbooks/common/aws_utils.py +2 -2
- runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
- runbooks/common/cross_account_manager.py +606 -0
- runbooks/common/date_utils.py +115 -0
- runbooks/common/enhanced_exception_handler.py +14 -7
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_cost_explorer_integration.py +5 -4
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +127 -72
- runbooks/common/rich_utils.py +3 -3
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/dashboard_runner.py +47 -28
- runbooks/finops/ebs_optimizer.py +56 -9
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +10 -4
- runbooks/finops/finops_dashboard.py +6 -5
- runbooks/finops/iam_guidance.py +6 -1
- runbooks/finops/markdown_exporter.py +217 -2
- runbooks/finops/nat_gateway_optimizer.py +76 -20
- runbooks/finops/tests/test_integration.py +3 -1
- runbooks/finops/vpc_cleanup_exporter.py +28 -26
- runbooks/finops/vpc_cleanup_optimizer.py +363 -16
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1177 -94
- runbooks/inventory/discovery.md +339 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +6 -9
- runbooks/inventory/list_ec2_instances.py +3 -3
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +104 -9
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1279 -0
- runbooks/inventory/verify_ec2_security_groups.py +3 -1
- runbooks/inventory/vpc_analyzer.py +825 -7
- runbooks/inventory/vpc_flow_analyzer.py +36 -42
- runbooks/main.py +708 -47
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/base.py +9 -6
- runbooks/operate/deployment_framework.py +5 -4
- runbooks/operate/deployment_validator.py +6 -5
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/mcp_integration.py +6 -5
- runbooks/operate/networking_cost_heatmap.py +21 -16
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +100 -12
- runbooks/remediation/base.py +4 -2
- runbooks/remediation/commons.py +5 -5
- runbooks/remediation/commvault_ec2_analysis.py +68 -15
- runbooks/remediation/config/accounts_example.json +31 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/multi_account.py +120 -7
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/remediation/remediation_cli.py +710 -0
- runbooks/remediation/universal_account_discovery.py +377 -0
- runbooks/security/compliance_automation_engine.py +99 -20
- runbooks/security/config/__init__.py +24 -0
- runbooks/security/config/compliance_config.py +255 -0
- runbooks/security/config/compliance_weights_example.json +22 -0
- runbooks/security/config_template_generator.py +500 -0
- runbooks/security/security_cli.py +377 -0
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/cli.py +8 -7
- runbooks/validation/comprehensive_2way_validator.py +2007 -0
- runbooks/validation/mcp_validator.py +965 -101
- runbooks/validation/terraform_citations_validator.py +363 -0
- runbooks/validation/terraform_drift_detector.py +1098 -0
- runbooks/vpc/cleanup_wrapper.py +231 -10
- runbooks/vpc/config.py +346 -73
- runbooks/vpc/cross_account_session.py +312 -0
- runbooks/vpc/heatmap_engine.py +115 -41
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1630 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks_adapter.py +33 -12
- runbooks/vpc/tests/conftest.py +4 -2
- runbooks/vpc/tests/test_cost_engine.py +4 -2
- runbooks/vpc/unified_scenarios.py +73 -3
- runbooks/vpc/vpc_cleanup_integration.py +512 -78
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/METADATA +94 -52
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/RECORD +101 -81
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
368
|
-
|
369
|
-
|
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
|
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
|
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
|
-
|
413
|
-
|
414
|
-
else:
|
415
|
-
print_warning(f"Could not access region {region}: {e}")
|
468
|
+
# Silently skip regions with no access
|
469
|
+
pass
|
416
470
|
|
417
|
-
|
418
|
-
|
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
|
-
#
|
976
|
-
#
|
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
|
-
|
979
|
-
|
980
|
-
|
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 = {}
|
runbooks/inventory/__init__.py
CHANGED
@@ -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
|