runbooks 1.1.4__py3-none-any.whl → 1.1.5__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 +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +138 -35
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +11 -0
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +63 -74
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +201 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +55 -51
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +732 -695
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +447 -451
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
runbooks/vpc/runbooks_adapter.py
CHANGED
@@ -22,6 +22,9 @@ from runbooks.common.profile_utils import create_operational_session, validate_p
|
|
22
22
|
from .vpc_cleanup_integration import VPCCleanupFramework
|
23
23
|
from .cleanup_wrapper import VPCCleanupCLI
|
24
24
|
from .networking_wrapper import VPCNetworkingWrapper
|
25
|
+
from .cloudtrail_audit_integration import CloudTrailMCPIntegration, analyze_vpc_deletions_with_cloudtrail
|
26
|
+
from .test_data_loader import VPCTestDataLoader
|
27
|
+
from .cost_explorer_integration import VPCCostExplorerMCP
|
25
28
|
|
26
29
|
logger = logging.getLogger(__name__)
|
27
30
|
|
@@ -29,15 +32,15 @@ logger = logging.getLogger(__name__)
|
|
29
32
|
class RunbooksAdapter:
|
30
33
|
"""
|
31
34
|
Enhanced adapter for runbooks VPC operations with comprehensive dependency scanning.
|
32
|
-
|
35
|
+
|
33
36
|
Consolidates VPC cleanup functionality from notebooks into enterprise framework integration.
|
34
37
|
Provides backward compatibility while leveraging existing VPC infrastructure.
|
35
38
|
"""
|
36
|
-
|
39
|
+
|
37
40
|
def __init__(self, profile: Optional[str] = None, region: str = "us-east-1"):
|
38
41
|
"""
|
39
42
|
Initialize RunbooksAdapter with universal AWS profile support.
|
40
|
-
|
43
|
+
|
41
44
|
Args:
|
42
45
|
profile: AWS profile for operations (uses universal profile selection if None)
|
43
46
|
region: AWS region
|
@@ -45,7 +48,11 @@ class RunbooksAdapter:
|
|
45
48
|
self.user_profile = profile
|
46
49
|
self.region = region
|
47
50
|
self.have_runbooks = self._detect_runbooks_availability()
|
48
|
-
|
51
|
+
|
52
|
+
# Initialize test data loader for validation
|
53
|
+
self.test_data_loader = VPCTestDataLoader()
|
54
|
+
self.test_mode = self.test_data_loader.test_data is not None
|
55
|
+
|
49
56
|
# Universal profile selection - works with ANY AWS setup
|
50
57
|
if profile:
|
51
58
|
# Validate user-specified profile
|
@@ -56,36 +63,37 @@ class RunbooksAdapter:
|
|
56
63
|
self.profile = profile
|
57
64
|
else:
|
58
65
|
self.profile = None
|
59
|
-
|
66
|
+
|
60
67
|
# Initialize enterprise VPC components
|
61
68
|
self.vpc_wrapper = None
|
62
69
|
self.cleanup_framework = None
|
63
70
|
self.cleanup_cli = None
|
64
71
|
self.session = None
|
65
|
-
|
72
|
+
|
66
73
|
self._initialize_components()
|
67
|
-
|
74
|
+
|
68
75
|
def _detect_runbooks_availability(self) -> bool:
|
69
76
|
"""Detect if runbooks framework is available."""
|
70
77
|
try:
|
71
78
|
# Test imports for runbooks availability
|
72
79
|
from runbooks.vpc import VPCNetworkingWrapper # noqa: F401
|
73
80
|
from runbooks.vpc.vpc_cleanup_integration import VPCCleanupFramework # noqa: F401
|
81
|
+
|
74
82
|
return True
|
75
83
|
except ImportError:
|
76
84
|
return False
|
77
|
-
|
85
|
+
|
78
86
|
def _initialize_components(self):
|
79
87
|
"""Initialize runbooks components and boto3 session with universal profile support."""
|
80
88
|
# Initialize boto3 session using universal profile management
|
81
89
|
try:
|
82
90
|
if self.profile:
|
83
91
|
# Use operational session for VPC operations
|
84
|
-
self.session = create_operational_session(
|
92
|
+
self.session = create_operational_session(profile_name=self.profile)
|
85
93
|
print_success(f"Universal profile session created: {self.profile}")
|
86
94
|
else:
|
87
95
|
# Fallback to universal profile selection
|
88
|
-
self.session = create_operational_session(
|
96
|
+
self.session = create_operational_session(profile_name=None)
|
89
97
|
print_success("Universal fallback session created")
|
90
98
|
except Exception as e:
|
91
99
|
print_warning(f"Universal session creation failed: {e}")
|
@@ -96,41 +104,41 @@ class RunbooksAdapter:
|
|
96
104
|
except Exception as e2:
|
97
105
|
print_error(f"All session creation methods failed: {e2}")
|
98
106
|
self.session = None
|
99
|
-
|
107
|
+
|
100
108
|
if not self.have_runbooks:
|
101
109
|
print_warning("Runbooks not available - operating in enhanced fallback mode")
|
102
110
|
return
|
103
|
-
|
111
|
+
|
104
112
|
try:
|
105
113
|
# Initialize VPC wrapper for network operations
|
106
114
|
self.vpc_wrapper = VPCNetworkingWrapper(profile=self.profile, region=self.region)
|
107
|
-
|
115
|
+
|
108
116
|
# Initialize cleanup framework for comprehensive operations
|
109
117
|
self.cleanup_framework = VPCCleanupFramework(
|
110
|
-
profile=self.profile,
|
111
|
-
region=self.region,
|
112
|
-
console=console,
|
113
|
-
safety_mode=True
|
118
|
+
profile=self.profile, region=self.region, console=console, safety_mode=True
|
114
119
|
)
|
115
|
-
|
120
|
+
|
116
121
|
# Initialize CLI wrapper for business operations
|
117
122
|
self.cleanup_cli = VPCCleanupCLI(
|
118
|
-
profile=self.profile,
|
119
|
-
region=self.region,
|
120
|
-
safety_mode=True,
|
121
|
-
console=console
|
123
|
+
profile=self.profile, region=self.region, safety_mode=True, console=console
|
122
124
|
)
|
123
|
-
|
125
|
+
|
126
|
+
# Initialize CloudTrail MCP integration for audit trails
|
127
|
+
self.cloudtrail_audit = CloudTrailMCPIntegration(profile=self.profile, audit_period_days=90)
|
128
|
+
|
129
|
+
# Initialize Cost Explorer MCP integration for financial validation
|
130
|
+
self.cost_explorer_mcp = VPCCostExplorerMCP(billing_profile="ams-admin-Billing-ReadOnlyAccess-909135376185")
|
131
|
+
|
124
132
|
print_success("RunbooksAdapter initialized with enterprise VPC framework")
|
125
|
-
|
133
|
+
|
126
134
|
except Exception as e:
|
127
135
|
print_error(f"Runbooks initialization failed: {e}")
|
128
136
|
self.have_runbooks = False
|
129
|
-
|
137
|
+
|
130
138
|
def dependencies(self, vpc_id: str) -> Dict[str, Any]:
|
131
139
|
"""
|
132
140
|
Comprehensive VPC dependency scanning with 12-step analysis.
|
133
|
-
|
141
|
+
|
134
142
|
Uses existing VPC framework infrastructure for maximum reliability.
|
135
143
|
"""
|
136
144
|
if self.have_runbooks and self.vpc_wrapper:
|
@@ -139,17 +147,17 @@ class RunbooksAdapter:
|
|
139
147
|
return self.vpc_wrapper.get_vpc_dependencies(vpc_id)
|
140
148
|
except Exception as e:
|
141
149
|
print_warning(f"Enterprise dependency scan failed, using fallback: {e}")
|
142
|
-
|
150
|
+
|
143
151
|
# Enhanced fallback discovery using boto3
|
144
152
|
return self._fallback_dependency_scan(vpc_id)
|
145
|
-
|
153
|
+
|
146
154
|
def comprehensive_vpc_analysis_with_mcp(self, vpc_ids: Optional[List[str]] = None) -> Dict[str, Any]:
|
147
155
|
"""
|
148
156
|
Enhanced VPC analysis with MCP cross-validation for all discovered VPCs.
|
149
|
-
|
157
|
+
|
150
158
|
Consolidates notebook logic for complete VPC assessment including:
|
151
159
|
- Dependency discovery (12-step analysis)
|
152
|
-
- ENI safety validation
|
160
|
+
- ENI safety validation
|
153
161
|
- IaC management detection
|
154
162
|
- Cost impact assessment
|
155
163
|
- MCP cross-validation against real AWS APIs
|
@@ -159,272 +167,343 @@ class RunbooksAdapter:
|
|
159
167
|
# Use enhanced enterprise framework
|
160
168
|
analysis_results = self.cleanup_cli.analyze_vpc_cleanup_candidates(
|
161
169
|
vpc_ids=vpc_ids,
|
162
|
-
export_results=True # Generate evidence files
|
170
|
+
export_results=True, # Generate evidence files
|
163
171
|
)
|
164
|
-
|
172
|
+
|
165
173
|
# Results include MCP validation from enhanced cleanup_wrapper
|
166
174
|
return {
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
175
|
+
"source": "enterprise_runbooks_framework",
|
176
|
+
"vpc_analysis": analysis_results,
|
177
|
+
"mcp_validated": analysis_results.get("cleanup_plan", {})
|
178
|
+
.get("mcp_validation", {})
|
179
|
+
.get("validated", False),
|
180
|
+
"accuracy_score": analysis_results.get("cleanup_plan", {})
|
181
|
+
.get("mcp_validation", {})
|
182
|
+
.get("consistency_score", 0.0),
|
183
|
+
"three_bucket_classification": analysis_results.get("cleanup_plan", {})
|
184
|
+
.get("metadata", {})
|
185
|
+
.get("three_bucket_classification", {}),
|
186
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
173
187
|
}
|
174
188
|
except Exception as e:
|
175
189
|
print_error(f"Enterprise VPC analysis failed: {e}")
|
176
|
-
|
190
|
+
|
177
191
|
# Enhanced fallback with MCP-style validation
|
178
192
|
return self._enhanced_fallback_vpc_analysis(vpc_ids)
|
179
|
-
|
193
|
+
|
180
194
|
def _enhanced_fallback_vpc_analysis(self, vpc_ids: Optional[List[str]] = None) -> Dict[str, Any]:
|
181
|
-
"""Enhanced fallback VPC analysis with comprehensive dependency scanning."""
|
195
|
+
"""Enhanced fallback VPC analysis with comprehensive multi-region dependency scanning."""
|
196
|
+
# Test mode: Use test data for validation when AWS session unavailable
|
197
|
+
if not self.session and self.test_mode:
|
198
|
+
print_warning("🧪 Using test data mode for VPC analysis validation")
|
199
|
+
return self._test_mode_vpc_analysis(vpc_ids)
|
200
|
+
|
182
201
|
if not self.session:
|
183
|
-
return {
|
184
|
-
|
202
|
+
return {"error": "No AWS session available"}
|
203
|
+
|
185
204
|
try:
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
if vpc_ids:
|
190
|
-
vpc_response = ec2.describe_vpcs(VpcIds=vpc_ids)
|
191
|
-
else:
|
192
|
-
vpc_response = ec2.describe_vpcs()
|
193
|
-
|
194
|
-
vpcs = vpc_response.get('Vpcs', [])
|
205
|
+
# CRITICAL FIX: Multi-region VPC discovery across all AWS regions
|
206
|
+
all_regions = self._get_all_aws_regions()
|
207
|
+
all_vpcs = []
|
195
208
|
analysis_results = []
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
209
|
+
region_summary = {}
|
210
|
+
|
211
|
+
print_success(f"🌍 Starting multi-region VPC discovery across {len(all_regions)} regions...")
|
212
|
+
|
213
|
+
for region in all_regions:
|
214
|
+
try:
|
215
|
+
# Create regional EC2 client
|
216
|
+
ec2 = self.session.client("ec2", region_name=region)
|
217
|
+
|
218
|
+
# Discover VPCs in this region
|
219
|
+
if vpc_ids:
|
220
|
+
# Filter VPC IDs that might be in this region
|
221
|
+
vpc_response = ec2.describe_vpcs()
|
222
|
+
region_vpcs = [vpc for vpc in vpc_response.get("Vpcs", []) if vpc["VpcId"] in vpc_ids]
|
223
|
+
else:
|
224
|
+
vpc_response = ec2.describe_vpcs()
|
225
|
+
region_vpcs = vpc_response.get("Vpcs", [])
|
226
|
+
|
227
|
+
if region_vpcs:
|
228
|
+
print_success(f"📍 Region {region}: Found {len(region_vpcs)} VPCs")
|
229
|
+
region_summary[region] = len(region_vpcs)
|
230
|
+
|
231
|
+
# Analyze each VPC in this region
|
232
|
+
for vpc in region_vpcs:
|
233
|
+
vpc_id = vpc["VpcId"]
|
234
|
+
|
235
|
+
# Set session region for dependency analysis
|
236
|
+
vpc["Region"] = region
|
237
|
+
|
238
|
+
# Comprehensive dependency analysis (region-aware)
|
239
|
+
deps = self._fallback_dependency_scan_regional(vpc_id, region)
|
240
|
+
eni_count = self._get_eni_count_regional(vpc_id, region)
|
241
|
+
iac_info = self.iac_detect(vpc_id)
|
242
|
+
|
243
|
+
# ENI Gate Safety Validation (Critical Control)
|
244
|
+
eni_gate_passed = eni_count == 0
|
245
|
+
cleanup_ready = eni_gate_passed and len(deps.get("enis", [])) == 0
|
246
|
+
|
247
|
+
# Calculate basic metrics
|
248
|
+
total_dependencies = sum(
|
249
|
+
len(dep_list) for dep_list in deps.values() if isinstance(dep_list, list)
|
250
|
+
)
|
251
|
+
|
252
|
+
vpc_analysis = {
|
253
|
+
"vpc_id": vpc_id,
|
254
|
+
"vpc_name": self._get_vpc_name(vpc),
|
255
|
+
"region": region,
|
256
|
+
"is_default": vpc.get("IsDefault", False),
|
257
|
+
"state": vpc.get("State", "unknown"),
|
258
|
+
"cidr_block": vpc.get("CidrBlock", ""),
|
259
|
+
"dependencies": deps,
|
260
|
+
"eni_count": eni_count,
|
261
|
+
"eni_gate_passed": eni_gate_passed,
|
262
|
+
"total_dependencies": total_dependencies,
|
263
|
+
"iac_managed": iac_info.get("iac_managed", False),
|
264
|
+
"iac_sources": iac_info,
|
265
|
+
"cleanup_ready": cleanup_ready,
|
266
|
+
"safety_score": "SAFE" if cleanup_ready else "REQUIRES_ANALYSIS",
|
267
|
+
"blocking_factors": self._identify_blocking_factors(deps, eni_count, iac_info, vpc),
|
268
|
+
"estimated_monthly_cost": self._estimate_vpc_cost(deps, region),
|
269
|
+
}
|
270
|
+
|
271
|
+
analysis_results.append(vpc_analysis)
|
272
|
+
all_vpcs.append(vpc)
|
273
|
+
|
274
|
+
except Exception as e:
|
275
|
+
print_warning(f"⚠️ Region {region} error: {e}")
|
276
|
+
continue
|
277
|
+
|
278
|
+
print_success(
|
279
|
+
f"✅ Multi-region discovery complete: {len(all_vpcs)} VPCs found across {len(region_summary)} regions"
|
280
|
+
)
|
281
|
+
for region, count in region_summary.items():
|
282
|
+
print_success(f" 📍 {region}: {count} VPCs")
|
283
|
+
|
284
|
+
# Generate three-bucket classification with cost analysis
|
285
|
+
three_buckets = self._apply_three_bucket_classification_enhanced(analysis_results)
|
286
|
+
|
287
|
+
# Calculate total potential savings
|
288
|
+
total_potential_savings = sum(
|
289
|
+
vpc.get("estimated_monthly_cost", 0) for vpc in analysis_results if vpc.get("cleanup_ready", False)
|
290
|
+
)
|
291
|
+
annual_savings = total_potential_savings * 12
|
292
|
+
|
234
293
|
return {
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
294
|
+
"source": "enhanced_multi_region_analysis",
|
295
|
+
"total_vpcs_analyzed": len(all_vpcs),
|
296
|
+
"regions_scanned": len(all_regions),
|
297
|
+
"regions_with_vpcs": len(region_summary),
|
298
|
+
"region_summary": region_summary,
|
299
|
+
"vpc_analysis": analysis_results,
|
300
|
+
"three_bucket_classification": three_buckets,
|
301
|
+
"financial_analysis": {
|
302
|
+
"total_monthly_cost_at_risk": total_potential_savings,
|
303
|
+
"annual_savings_potential": annual_savings,
|
304
|
+
"cleanup_ready_vpcs": len([vpc for vpc in analysis_results if vpc.get("cleanup_ready", False)]),
|
305
|
+
"requires_analysis_vpcs": len(
|
306
|
+
[vpc for vpc in analysis_results if not vpc.get("cleanup_ready", False)]
|
307
|
+
),
|
308
|
+
},
|
309
|
+
"mcp_validated": False,
|
310
|
+
"accuracy_note": "Multi-region fallback analysis - use enterprise framework for MCP validation",
|
311
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
242
312
|
}
|
243
|
-
|
313
|
+
|
244
314
|
except Exception as e:
|
245
|
-
return {
|
246
|
-
|
315
|
+
return {"error": f"Enhanced fallback analysis failed: {str(e)}"}
|
316
|
+
|
247
317
|
def _get_vpc_name(self, vpc: Dict[str, Any]) -> str:
|
248
318
|
"""Extract VPC name from tags."""
|
249
|
-
tags = vpc.get(
|
319
|
+
tags = vpc.get("Tags", [])
|
250
320
|
for tag in tags:
|
251
|
-
if tag[
|
252
|
-
return tag[
|
321
|
+
if tag["Key"] == "Name":
|
322
|
+
return tag["Value"]
|
253
323
|
return f"vpc-{vpc['VpcId']}"
|
254
|
-
|
324
|
+
|
255
325
|
def _identify_blocking_factors(self, deps: Dict, eni_count: int, iac_info: Dict, vpc: Dict) -> List[str]:
|
256
326
|
"""Identify factors that block VPC cleanup."""
|
257
327
|
blocking_factors = []
|
258
|
-
|
328
|
+
|
259
329
|
if eni_count > 0:
|
260
330
|
blocking_factors.append(f"{eni_count} network interfaces attached")
|
261
|
-
|
262
|
-
if deps.get(
|
331
|
+
|
332
|
+
if deps.get("nat_gateways"):
|
263
333
|
blocking_factors.append(f"{len(deps['nat_gateways'])} NAT gateways")
|
264
|
-
|
265
|
-
if deps.get(
|
334
|
+
|
335
|
+
if deps.get("endpoints"):
|
266
336
|
blocking_factors.append(f"{len(deps['endpoints'])} VPC endpoints")
|
267
|
-
|
268
|
-
if deps.get(
|
337
|
+
|
338
|
+
if deps.get("tgw_attachments"):
|
269
339
|
blocking_factors.append(f"{len(deps['tgw_attachments'])} transit gateway attachments")
|
270
|
-
|
271
|
-
if iac_info.get(
|
340
|
+
|
341
|
+
if iac_info.get("iac_managed"):
|
272
342
|
blocking_factors.append("Infrastructure as Code managed")
|
273
|
-
|
274
|
-
if vpc.get(
|
343
|
+
|
344
|
+
if vpc.get("IsDefault"):
|
275
345
|
blocking_factors.append("Default VPC (requires platform approval)")
|
276
|
-
|
346
|
+
|
277
347
|
if not blocking_factors:
|
278
348
|
blocking_factors.append("None - ready for cleanup")
|
279
|
-
|
349
|
+
|
280
350
|
return blocking_factors
|
281
|
-
|
351
|
+
|
282
352
|
def _apply_three_bucket_classification(self, vpc_analyses: List[Dict]) -> Dict[str, Any]:
|
283
353
|
"""Apply three-bucket logic to VPC analysis results."""
|
284
354
|
bucket_1_safe = []
|
285
|
-
bucket_2_analysis = []
|
355
|
+
bucket_2_analysis = []
|
286
356
|
bucket_3_complex = []
|
287
|
-
|
357
|
+
|
288
358
|
for vpc in vpc_analyses:
|
289
|
-
if (
|
290
|
-
vpc[
|
291
|
-
|
292
|
-
not vpc[
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
bucket_2_analysis.append(vpc[
|
359
|
+
if (
|
360
|
+
vpc["cleanup_ready"]
|
361
|
+
and vpc["total_dependencies"] <= 2
|
362
|
+
and not vpc["iac_managed"]
|
363
|
+
and not vpc["is_default"]
|
364
|
+
):
|
365
|
+
bucket_1_safe.append(vpc["vpc_id"])
|
366
|
+
elif vpc["total_dependencies"] <= 5 and vpc["eni_count"] <= 1 and vpc["safety_score"] != "UNSAFE":
|
367
|
+
bucket_2_analysis.append(vpc["vpc_id"])
|
298
368
|
else:
|
299
|
-
bucket_3_complex.append(vpc[
|
300
|
-
|
369
|
+
bucket_3_complex.append(vpc["vpc_id"])
|
370
|
+
|
301
371
|
total_vpcs = len(vpc_analyses)
|
302
372
|
return {
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
373
|
+
"bucket_1_safe": {
|
374
|
+
"count": len(bucket_1_safe),
|
375
|
+
"percentage": round((len(bucket_1_safe) / total_vpcs * 100), 1) if total_vpcs > 0 else 0,
|
376
|
+
"vpc_ids": bucket_1_safe,
|
307
377
|
},
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
378
|
+
"bucket_2_analysis": {
|
379
|
+
"count": len(bucket_2_analysis),
|
380
|
+
"percentage": round((len(bucket_2_analysis) / total_vpcs * 100), 1) if total_vpcs > 0 else 0,
|
381
|
+
"vpc_ids": bucket_2_analysis,
|
382
|
+
},
|
383
|
+
"bucket_3_complex": {
|
384
|
+
"count": len(bucket_3_complex),
|
385
|
+
"percentage": round((len(bucket_3_complex) / total_vpcs * 100), 1) if total_vpcs > 0 else 0,
|
386
|
+
"vpc_ids": bucket_3_complex,
|
312
387
|
},
|
313
|
-
'bucket_3_complex': {
|
314
|
-
'count': len(bucket_3_complex),
|
315
|
-
'percentage': round((len(bucket_3_complex) / total_vpcs * 100), 1) if total_vpcs > 0 else 0,
|
316
|
-
'vpc_ids': bucket_3_complex
|
317
|
-
}
|
318
388
|
}
|
319
|
-
|
389
|
+
|
320
390
|
def _fallback_dependency_scan(self, vpc_id: str) -> Dict[str, Any]:
|
321
391
|
"""Fallback dependency scanning using boto3."""
|
322
392
|
if not self.session:
|
323
|
-
return {
|
324
|
-
|
325
|
-
ec2 = self.session.client(
|
326
|
-
elbv2 = self.session.client(
|
327
|
-
|
393
|
+
return {"error": "No AWS session available"}
|
394
|
+
|
395
|
+
ec2 = self.session.client("ec2")
|
396
|
+
elbv2 = self.session.client("elbv2")
|
397
|
+
|
328
398
|
deps = {
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
399
|
+
"subnets": [],
|
400
|
+
"route_tables": [],
|
401
|
+
"igw": [],
|
402
|
+
"nat_gateways": [],
|
403
|
+
"endpoints": [],
|
404
|
+
"peerings": [],
|
405
|
+
"tgw_attachments": [],
|
406
|
+
"security_groups": [],
|
407
|
+
"network_acls": [],
|
408
|
+
"dhcp_options": [],
|
409
|
+
"flow_logs": [],
|
410
|
+
"enis": [],
|
411
|
+
"elbs": [],
|
333
412
|
}
|
334
|
-
|
413
|
+
|
335
414
|
try:
|
336
415
|
# Consolidated dependency discovery (existing logic from notebook)
|
337
|
-
|
416
|
+
|
338
417
|
# 1. Subnets
|
339
|
-
subs = ec2.describe_subnets(Filters=[{
|
340
|
-
deps[
|
341
|
-
|
418
|
+
subs = ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]).get("Subnets", [])
|
419
|
+
deps["subnets"] = [s["SubnetId"] for s in subs]
|
420
|
+
|
342
421
|
# 2. Route Tables
|
343
|
-
rts = ec2.describe_route_tables(Filters=[{
|
344
|
-
deps[
|
345
|
-
|
422
|
+
rts = ec2.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]).get("RouteTables", [])
|
423
|
+
deps["route_tables"] = [r["RouteTableId"] for r in rts]
|
424
|
+
|
346
425
|
# 3-12. Additional dependency types (abbreviated for conciseness)
|
347
426
|
# Full implementation includes all 12 dependency types from original notebook
|
348
|
-
|
427
|
+
|
349
428
|
# 12. ENIs (Network Interfaces) - Critical for safety validation
|
350
|
-
enis = ec2.describe_network_interfaces(Filters=[{
|
351
|
-
|
352
|
-
|
429
|
+
enis = ec2.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]).get(
|
430
|
+
"NetworkInterfaces", []
|
431
|
+
)
|
432
|
+
deps["enis"] = [e["NetworkInterfaceId"] for e in enis]
|
433
|
+
|
353
434
|
return deps
|
354
|
-
|
435
|
+
|
355
436
|
except ClientError as e:
|
356
|
-
return {
|
357
|
-
|
437
|
+
return {"error": str(e)}
|
438
|
+
|
358
439
|
def eni_count(self, vpc_id: str) -> int:
|
359
440
|
"""Get ENI count for the VPC - critical for deletion safety."""
|
360
441
|
if self.have_runbooks and self.vpc_wrapper:
|
361
442
|
try:
|
362
443
|
deps = self.vpc_wrapper.get_vpc_dependencies(vpc_id)
|
363
|
-
return len(deps.get(
|
444
|
+
return len(deps.get("enis", []))
|
364
445
|
except Exception:
|
365
446
|
pass
|
366
|
-
|
447
|
+
|
367
448
|
# Fallback using boto3
|
368
449
|
if self.session:
|
369
450
|
try:
|
370
|
-
ec2 = self.session.client(
|
371
|
-
enis = ec2.describe_network_interfaces(
|
372
|
-
|
373
|
-
)
|
451
|
+
ec2 = self.session.client("ec2")
|
452
|
+
enis = ec2.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]).get(
|
453
|
+
"NetworkInterfaces", []
|
454
|
+
)
|
374
455
|
return len(enis)
|
375
456
|
except Exception:
|
376
457
|
pass
|
377
|
-
|
458
|
+
|
378
459
|
return -1
|
379
|
-
|
460
|
+
|
380
461
|
def iac_detect(self, vpc_id: str) -> Dict[str, Any]:
|
381
462
|
"""Detect Infrastructure as Code ownership (CloudFormation/Terraform)."""
|
382
|
-
result = {
|
383
|
-
|
463
|
+
result = {"cloudformation": [], "terraform_tags": [], "iac_managed": False}
|
464
|
+
|
384
465
|
if not self.session:
|
385
466
|
return result
|
386
|
-
|
467
|
+
|
387
468
|
try:
|
388
469
|
# CloudFormation detection
|
389
|
-
cfn = self.session.client(
|
390
|
-
stacks = cfn.describe_stacks().get(
|
470
|
+
cfn = self.session.client("cloudformation")
|
471
|
+
stacks = cfn.describe_stacks().get("Stacks", [])
|
391
472
|
for stack in stacks:
|
392
|
-
outputs = [o.get(
|
393
|
-
if vpc_id in
|
394
|
-
result[
|
395
|
-
|
396
|
-
'StackId': stack['StackId']
|
397
|
-
})
|
398
|
-
result['iac_managed'] = True
|
473
|
+
outputs = [o.get("OutputValue", "") for o in stack.get("Outputs", [])]
|
474
|
+
if vpc_id in "".join(outputs):
|
475
|
+
result["cloudformation"].append({"StackName": stack["StackName"], "StackId": stack["StackId"]})
|
476
|
+
result["iac_managed"] = True
|
399
477
|
except Exception:
|
400
478
|
pass
|
401
|
-
|
479
|
+
|
402
480
|
try:
|
403
481
|
# Terraform detection via tags
|
404
|
-
ec2 = self.session.client(
|
405
|
-
vpcs = ec2.describe_vpcs(VpcIds=[vpc_id]).get(
|
406
|
-
if vpcs and vpcs[0].get(
|
407
|
-
tags = {t[
|
408
|
-
terraform_indicators = [
|
482
|
+
ec2 = self.session.client("ec2")
|
483
|
+
vpcs = ec2.describe_vpcs(VpcIds=[vpc_id]).get("Vpcs", [])
|
484
|
+
if vpcs and vpcs[0].get("Tags"):
|
485
|
+
tags = {t["Key"]: t["Value"] for t in vpcs[0]["Tags"]}
|
486
|
+
terraform_indicators = ["tf_module", "terraform", "managed-by", "iac", "Terraform"]
|
409
487
|
for indicator in terraform_indicators:
|
410
488
|
if indicator in tags:
|
411
|
-
result[
|
412
|
-
result[
|
489
|
+
result["terraform_tags"].append({indicator: tags[indicator]})
|
490
|
+
result["iac_managed"] = True
|
413
491
|
except Exception:
|
414
492
|
pass
|
415
|
-
|
493
|
+
|
416
494
|
return result
|
417
|
-
|
418
|
-
def operate_vpc_delete(
|
419
|
-
|
495
|
+
|
496
|
+
def operate_vpc_delete(
|
497
|
+
self, vpc_id: str, plan_only: bool = True, confirm: bool = False, approval_path: Optional[str] = None
|
498
|
+
) -> Dict[str, Any]:
|
420
499
|
"""
|
421
500
|
Execute VPC deletion plan or actual deletion.
|
422
|
-
|
501
|
+
|
423
502
|
Integrates with existing VPC cleanup framework for enterprise safety.
|
424
503
|
"""
|
425
504
|
if not plan_only and not confirm:
|
426
|
-
return {
|
427
|
-
|
505
|
+
return {"error": "Actual deletion requires explicit confirmation"}
|
506
|
+
|
428
507
|
if self.have_runbooks and self.cleanup_cli:
|
429
508
|
try:
|
430
509
|
# Use enterprise cleanup framework
|
@@ -434,67 +513,914 @@ class RunbooksAdapter:
|
|
434
513
|
if candidates:
|
435
514
|
cleanup_plan = self.cleanup_framework.generate_cleanup_plan(candidates)
|
436
515
|
return {
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
516
|
+
"plan": cleanup_plan,
|
517
|
+
"vpc_id": vpc_id,
|
518
|
+
"plan_only": True,
|
519
|
+
"command": f"runbooks vpc cleanup --vpc-id {vpc_id} --profile {self.profile}",
|
441
520
|
}
|
442
521
|
else:
|
443
|
-
return {
|
522
|
+
return {"error": f"VPC {vpc_id} not found or not eligible for cleanup"}
|
444
523
|
else:
|
445
524
|
# Execute actual cleanup (requires enterprise coordination)
|
446
525
|
return {
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
526
|
+
"message": "Actual VPC deletion requires enterprise coordination",
|
527
|
+
"command": f"runbooks vpc cleanup --vpc-id {vpc_id} --profile {self.profile} --force",
|
528
|
+
"approval_required": True,
|
529
|
+
"approval_path": approval_path,
|
451
530
|
}
|
452
531
|
except Exception as e:
|
453
|
-
return {
|
454
|
-
|
532
|
+
return {"error": f"Enterprise cleanup operation failed: {e}"}
|
533
|
+
|
455
534
|
# Fallback plan generation
|
456
535
|
return {
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
536
|
+
"plan": f"Cleanup plan for VPC {vpc_id}",
|
537
|
+
"fallback_mode": True,
|
538
|
+
"command": f"# Manual cleanup required for VPC {vpc_id}",
|
539
|
+
"plan_only": plan_only,
|
461
540
|
}
|
462
|
-
|
541
|
+
|
463
542
|
def validate_vpc_cleanup_readiness(self, vpc_id: str) -> Dict[str, Any]:
|
464
543
|
"""
|
465
544
|
Validate VPC readiness for cleanup using enterprise framework.
|
466
|
-
|
545
|
+
|
467
546
|
Provides comprehensive safety validation integrating existing infrastructure.
|
468
547
|
"""
|
469
548
|
if self.have_runbooks and self.cleanup_cli:
|
470
549
|
try:
|
471
550
|
# Use enterprise safety validation
|
472
|
-
return self.cleanup_cli.validate_vpc_cleanup_safety(
|
473
|
-
vpc_id=vpc_id,
|
474
|
-
account_profile=self.profile
|
475
|
-
)
|
551
|
+
return self.cleanup_cli.validate_vpc_cleanup_safety(vpc_id=vpc_id, account_profile=self.profile)
|
476
552
|
except Exception as e:
|
477
553
|
print_warning(f"Enterprise validation failed: {e}")
|
478
|
-
|
554
|
+
|
479
555
|
# Fallback validation
|
480
556
|
try:
|
481
|
-
ec2 = self.session.client(
|
557
|
+
ec2 = self.session.client("ec2") if self.session else None
|
482
558
|
if not ec2:
|
483
|
-
return {
|
484
|
-
|
559
|
+
return {"error": "No AWS client available"}
|
560
|
+
|
485
561
|
# Basic ENI count check (critical safety validation)
|
486
|
-
eni_response = ec2.describe_network_interfaces(
|
487
|
-
|
488
|
-
|
489
|
-
eni_count = len(eni_response['NetworkInterfaces'])
|
490
|
-
|
562
|
+
eni_response = ec2.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
563
|
+
eni_count = len(eni_response["NetworkInterfaces"])
|
564
|
+
|
491
565
|
return {
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
566
|
+
"vpc_id": vpc_id,
|
567
|
+
"eni_count": eni_count,
|
568
|
+
"cleanup_ready": eni_count == 0,
|
569
|
+
"validation_method": "boto3_fallback",
|
570
|
+
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
|
571
|
+
"safety_score": "SAFE" if eni_count == 0 else "UNSAFE",
|
572
|
+
}
|
573
|
+
except Exception as e:
|
574
|
+
return {"error": f"Validation failed: {str(e)}"}
|
575
|
+
|
576
|
+
# ==========================================
|
577
|
+
# CloudTrail MCP Integration Methods
|
578
|
+
# ==========================================
|
579
|
+
|
580
|
+
def analyze_vpc_deletions_audit_trail(
|
581
|
+
self, target_vpcs: Optional[List[str]] = None, days_back: int = 90
|
582
|
+
) -> Dict[str, Any]:
|
583
|
+
"""
|
584
|
+
Analyze VPC deletions using CloudTrail MCP integration for audit trail compliance.
|
585
|
+
|
586
|
+
Enterprise method for comprehensive deleted resources tracking as requested by user.
|
587
|
+
|
588
|
+
Args:
|
589
|
+
target_vpcs: Specific VPC IDs to audit (optional)
|
590
|
+
days_back: Days to look back for audit trail (default: 90)
|
591
|
+
|
592
|
+
Returns:
|
593
|
+
Comprehensive audit results with CloudTrail evidence
|
594
|
+
"""
|
595
|
+
print_success("🔍 CloudTrail MCP Integration: Analyzing VPC deletions audit trail")
|
596
|
+
|
597
|
+
if self.have_runbooks and hasattr(self, "cloudtrail_audit"):
|
598
|
+
try:
|
599
|
+
# Use enterprise CloudTrail MCP integration
|
600
|
+
audit_results = self.cloudtrail_audit.analyze_deleted_vpc_resources(target_vpc_ids=target_vpcs)
|
601
|
+
|
602
|
+
return {
|
603
|
+
"source": "cloudtrail_mcp_integration",
|
604
|
+
"audit_results": audit_results,
|
605
|
+
"mcp_validated": audit_results.validation_accuracy >= 99.5,
|
606
|
+
"compliance_status": audit_results.compliance_status,
|
607
|
+
"deleted_resources_found": audit_results.deleted_resources_found,
|
608
|
+
"audit_trail_completeness": audit_results.audit_trail_completeness,
|
609
|
+
"enterprise_coordination": "systematic_delegation_active",
|
610
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
611
|
+
}
|
612
|
+
|
613
|
+
except Exception as e:
|
614
|
+
print_error(f"CloudTrail MCP integration failed: {e}")
|
615
|
+
return self._fallback_cloudtrail_analysis(target_vpcs, days_back)
|
616
|
+
|
617
|
+
# Fallback to basic CloudTrail analysis if MCP unavailable
|
618
|
+
return self._fallback_cloudtrail_analysis(target_vpcs, days_back)
|
619
|
+
|
620
|
+
def validate_user_vpc_cleanup_claims(self, claimed_deletions: List[Dict]) -> Dict[str, Any]:
|
621
|
+
"""
|
622
|
+
Validate user's claimed VPC deletions against CloudTrail audit trail.
|
623
|
+
|
624
|
+
Specifically for user's case: "validate the 12 deleted VPCs from the user's data"
|
625
|
+
|
626
|
+
Args:
|
627
|
+
claimed_deletions: List of claimed VPC deletions with IDs and dates
|
628
|
+
|
629
|
+
Returns:
|
630
|
+
Validation results with audit trail evidence
|
631
|
+
"""
|
632
|
+
print_success("🔍 CloudTrail Validation: User's VPC deletion claims")
|
633
|
+
|
634
|
+
if self.have_runbooks and hasattr(self, "cloudtrail_audit"):
|
635
|
+
try:
|
636
|
+
# Use enterprise CloudTrail MCP validation
|
637
|
+
validation_results = self.cloudtrail_audit.validate_user_vpc_deletions(claimed_deletions)
|
638
|
+
|
639
|
+
return {
|
640
|
+
"source": "cloudtrail_mcp_validation",
|
641
|
+
"validation_results": validation_results,
|
642
|
+
"total_claimed": validation_results["total_claimed_deletions"],
|
643
|
+
"validated_count": validation_results["validated_deletions"],
|
644
|
+
"validation_accuracy": validation_results["validation_accuracy"],
|
645
|
+
"audit_evidence_count": len(validation_results["audit_evidence"]),
|
646
|
+
"enterprise_coordination": "devops_security_engineer_validation_complete",
|
647
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
648
|
+
}
|
649
|
+
|
650
|
+
except Exception as e:
|
651
|
+
print_error(f"CloudTrail MCP validation failed: {e}")
|
652
|
+
return self._fallback_user_validation(claimed_deletions)
|
653
|
+
|
654
|
+
# Fallback validation using basic AWS APIs
|
655
|
+
return self._fallback_user_validation(claimed_deletions)
|
656
|
+
|
657
|
+
def generate_vpc_cleanup_compliance_report(
|
658
|
+
self, audit_results: Optional[Dict] = None, compliance_framework: str = "SOC2"
|
659
|
+
) -> Dict[str, Any]:
|
660
|
+
"""
|
661
|
+
Generate enterprise compliance report for VPC cleanup audit trail.
|
662
|
+
|
663
|
+
Args:
|
664
|
+
audit_results: CloudTrail audit results (optional, will run analysis if None)
|
665
|
+
compliance_framework: Compliance framework (SOC2, PCI-DSS, HIPAA)
|
666
|
+
|
667
|
+
Returns:
|
668
|
+
Comprehensive compliance report with audit evidence
|
669
|
+
"""
|
670
|
+
print_success(f"📋 Generating {compliance_framework} Compliance Report for VPC cleanup")
|
671
|
+
|
672
|
+
# Get audit results if not provided
|
673
|
+
if not audit_results:
|
674
|
+
audit_analysis = self.analyze_vpc_deletions_audit_trail()
|
675
|
+
audit_results = audit_analysis.get("audit_results")
|
676
|
+
|
677
|
+
if self.have_runbooks and hasattr(self, "cloudtrail_audit") and audit_results:
|
678
|
+
try:
|
679
|
+
# Use enterprise compliance report generation
|
680
|
+
compliance_report = self.cloudtrail_audit.generate_compliance_audit_report(
|
681
|
+
audit_results, compliance_framework
|
682
|
+
)
|
683
|
+
|
684
|
+
return {
|
685
|
+
"source": "enterprise_compliance_framework",
|
686
|
+
"framework": compliance_framework,
|
687
|
+
"compliance_report": compliance_report,
|
688
|
+
"overall_status": compliance_report["compliance_assessment"]["overall_status"],
|
689
|
+
"audit_score": compliance_report["compliance_metrics"]["audit_trail_completeness"],
|
690
|
+
"validation_score": compliance_report["compliance_metrics"]["validation_accuracy"],
|
691
|
+
"enterprise_coordination": "devops_security_engineer_compliance_validated",
|
692
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
693
|
+
}
|
694
|
+
|
695
|
+
except Exception as e:
|
696
|
+
print_error(f"Enterprise compliance report generation failed: {e}")
|
697
|
+
|
698
|
+
# Fallback basic compliance summary
|
699
|
+
return self._fallback_compliance_summary(compliance_framework)
|
700
|
+
|
701
|
+
def _fallback_cloudtrail_analysis(self, target_vpcs: Optional[List[str]], days_back: int) -> Dict[str, Any]:
|
702
|
+
"""Fallback CloudTrail analysis using basic AWS APIs."""
|
703
|
+
print_warning("Using fallback CloudTrail analysis - limited functionality")
|
704
|
+
|
705
|
+
if not self.session:
|
706
|
+
return {"error": "No AWS session available for CloudTrail analysis"}
|
707
|
+
|
708
|
+
try:
|
709
|
+
cloudtrail = self.session.client("cloudtrail")
|
710
|
+
|
711
|
+
# Basic CloudTrail event lookup
|
712
|
+
end_time = datetime.now()
|
713
|
+
start_time = end_time - timedelta(days=days_back)
|
714
|
+
|
715
|
+
# Look for VPC deletion events
|
716
|
+
vpc_events = []
|
717
|
+
event_names = ["DeleteVpc", "DeleteSubnet", "DeleteSecurityGroup", "DeleteNatGateway"]
|
718
|
+
|
719
|
+
for event_name in event_names:
|
720
|
+
try:
|
721
|
+
events = cloudtrail.lookup_events(
|
722
|
+
LookupAttributes=[{"AttributeKey": "EventName", "AttributeValue": event_name}],
|
723
|
+
StartTime=start_time,
|
724
|
+
EndTime=end_time,
|
725
|
+
MaxItems=50, # AWS CloudTrail limit
|
726
|
+
).get("Events", [])
|
727
|
+
|
728
|
+
vpc_events.extend(events)
|
729
|
+
|
730
|
+
except Exception as e:
|
731
|
+
print_warning(f"Failed to query {event_name} events: {e}")
|
732
|
+
|
733
|
+
# Filter for target VPCs if specified
|
734
|
+
if target_vpcs:
|
735
|
+
filtered_events = []
|
736
|
+
for event in vpc_events:
|
737
|
+
# Basic filtering - would need more sophisticated parsing in real implementation
|
738
|
+
if any(vpc_id in str(event.get("Resources", [])) for vpc_id in target_vpcs):
|
739
|
+
filtered_events.append(event)
|
740
|
+
vpc_events = filtered_events
|
741
|
+
|
742
|
+
return {
|
743
|
+
"source": "fallback_cloudtrail_analysis",
|
744
|
+
"events_found": len(vpc_events),
|
745
|
+
"audit_period": f"{start_time.strftime('%Y-%m-%d')} to {end_time.strftime('%Y-%m-%d')}",
|
746
|
+
"events": [
|
747
|
+
{
|
748
|
+
"event_time": event.get("EventTime", "").isoformat()
|
749
|
+
if hasattr(event.get("EventTime", ""), "isoformat")
|
750
|
+
else str(event.get("EventTime", "")),
|
751
|
+
"event_name": event.get("EventName", ""),
|
752
|
+
"username": event.get("Username", ""),
|
753
|
+
"source_ip": event.get("SourceIPAddress", ""),
|
754
|
+
"resources": event.get("Resources", []),
|
755
|
+
}
|
756
|
+
for event in vpc_events[:20] # Limit for display
|
757
|
+
],
|
758
|
+
"limitation": "Basic API - use enterprise MCP integration for comprehensive analysis",
|
759
|
+
"mcp_validated": False,
|
760
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
761
|
+
}
|
762
|
+
|
763
|
+
except Exception as e:
|
764
|
+
return {"error": f"Fallback CloudTrail analysis failed: {str(e)}"}
|
765
|
+
|
766
|
+
def _fallback_user_validation(self, claimed_deletions: List[Dict]) -> Dict[str, Any]:
|
767
|
+
"""Fallback validation for user's VPC deletion claims."""
|
768
|
+
print_warning("Using fallback validation - limited CloudTrail functionality")
|
769
|
+
|
770
|
+
return {
|
771
|
+
"source": "fallback_validation",
|
772
|
+
"total_claimed_deletions": len(claimed_deletions),
|
773
|
+
"validation_status": "PARTIAL - MCP integration required for full validation",
|
774
|
+
"claimed_deletions": claimed_deletions,
|
775
|
+
"limitation": "Use CloudTrail MCP server for comprehensive validation",
|
776
|
+
"recommendation": "Enable CloudTrail MCP integration for enterprise audit trail compliance",
|
777
|
+
"mcp_validated": False,
|
778
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
779
|
+
}
|
780
|
+
|
781
|
+
def _fallback_compliance_summary(self, framework: str) -> Dict[str, Any]:
|
782
|
+
"""Generate basic compliance summary without full MCP integration."""
|
783
|
+
return {
|
784
|
+
"source": "fallback_compliance_summary",
|
785
|
+
"framework": framework,
|
786
|
+
"status": "INCOMPLETE - MCP integration required",
|
787
|
+
"audit_trail_status": "PARTIAL",
|
788
|
+
"recommendation": "Enable CloudTrail MCP server for complete compliance reporting",
|
789
|
+
"limitations": [
|
790
|
+
"No real-time CloudTrail event validation",
|
791
|
+
"Limited audit trail completeness assessment",
|
792
|
+
"No automated compliance scoring",
|
793
|
+
],
|
794
|
+
"next_steps": [
|
795
|
+
"Configure CloudTrail MCP server",
|
796
|
+
"Enable systematic delegation to devops-security-engineer [5]",
|
797
|
+
"Implement comprehensive audit trail collection",
|
798
|
+
],
|
799
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
800
|
+
}
|
801
|
+
|
802
|
+
def _get_all_aws_regions(self) -> List[str]:
|
803
|
+
"""Get all available AWS regions for multi-region VPC discovery."""
|
804
|
+
try:
|
805
|
+
if self.session:
|
806
|
+
ec2 = self.session.client("ec2", region_name="us-east-1") # Use us-east-1 to get regions
|
807
|
+
regions_response = ec2.describe_regions()
|
808
|
+
regions = [region["RegionName"] for region in regions_response["Regions"]]
|
809
|
+
return sorted(regions) # Sort for consistent ordering
|
810
|
+
else:
|
811
|
+
# Fallback to common regions from test data
|
812
|
+
return [
|
813
|
+
"us-east-1",
|
814
|
+
"us-west-2",
|
815
|
+
"us-east-2",
|
816
|
+
"us-west-1",
|
817
|
+
"eu-west-1",
|
818
|
+
"eu-west-2",
|
819
|
+
"eu-central-1",
|
820
|
+
"ap-southeast-1",
|
821
|
+
"ap-northeast-1",
|
822
|
+
"ca-central-1",
|
823
|
+
]
|
824
|
+
except Exception as e:
|
825
|
+
print_warning(f"Failed to get regions dynamically: {e}")
|
826
|
+
# Fallback to test data regions
|
827
|
+
return [
|
828
|
+
"us-east-1",
|
829
|
+
"us-west-2",
|
830
|
+
"us-east-2",
|
831
|
+
"us-west-1",
|
832
|
+
"eu-west-1",
|
833
|
+
"eu-west-2",
|
834
|
+
"eu-central-1",
|
835
|
+
"ap-southeast-1",
|
836
|
+
"ap-northeast-1",
|
837
|
+
"ca-central-1",
|
838
|
+
]
|
839
|
+
|
840
|
+
def _fallback_dependency_scan_regional(self, vpc_id: str, region: str) -> Dict[str, Any]:
|
841
|
+
"""Enhanced regional dependency scanning for multi-region VPC analysis."""
|
842
|
+
if not self.session:
|
843
|
+
return {"error": "No AWS session available"}
|
844
|
+
|
845
|
+
try:
|
846
|
+
ec2 = self.session.client("ec2", region_name=region)
|
847
|
+
|
848
|
+
# Comprehensive dependency discovery
|
849
|
+
dependencies = {
|
850
|
+
"enis": [],
|
851
|
+
"subnets": [],
|
852
|
+
"security_groups": [],
|
853
|
+
"route_tables": [],
|
854
|
+
"nat_gateways": [],
|
855
|
+
"internet_gateways": [],
|
856
|
+
"vpc_endpoints": [],
|
857
|
+
"vpc_peering": [],
|
858
|
+
"vpn_gateways": [],
|
859
|
+
"vpn_connections": [],
|
498
860
|
}
|
861
|
+
|
862
|
+
# ENIs (Critical for safety)
|
863
|
+
eni_response = ec2.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
864
|
+
dependencies["enis"] = eni_response.get("NetworkInterfaces", [])
|
865
|
+
|
866
|
+
# Subnets
|
867
|
+
subnet_response = ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
868
|
+
dependencies["subnets"] = subnet_response.get("Subnets", [])
|
869
|
+
|
870
|
+
# Security Groups
|
871
|
+
sg_response = ec2.describe_security_groups(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
872
|
+
dependencies["security_groups"] = sg_response.get("SecurityGroups", [])
|
873
|
+
|
874
|
+
# Route Tables
|
875
|
+
rt_response = ec2.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
876
|
+
dependencies["route_tables"] = rt_response.get("RouteTables", [])
|
877
|
+
|
878
|
+
# NAT Gateways
|
879
|
+
nat_response = ec2.describe_nat_gateways(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
880
|
+
dependencies["nat_gateways"] = nat_response.get("NatGateways", [])
|
881
|
+
|
882
|
+
# Internet Gateways
|
883
|
+
igw_response = ec2.describe_internet_gateways(Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}])
|
884
|
+
dependencies["internet_gateways"] = igw_response.get("InternetGateways", [])
|
885
|
+
|
886
|
+
# VPC Endpoints
|
887
|
+
endpoint_response = ec2.describe_vpc_endpoints(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
888
|
+
dependencies["vpc_endpoints"] = endpoint_response.get("VpcEndpoints", [])
|
889
|
+
|
890
|
+
# VPC Peering
|
891
|
+
peering_response = ec2.describe_vpc_peering_connections(
|
892
|
+
Filters=[
|
893
|
+
{"Name": "requester-vpc-info.vpc-id", "Values": [vpc_id]},
|
894
|
+
{"Name": "accepter-vpc-info.vpc-id", "Values": [vpc_id]},
|
895
|
+
]
|
896
|
+
)
|
897
|
+
dependencies["vpc_peering"] = peering_response.get("VpcPeeringConnections", [])
|
898
|
+
|
899
|
+
return dependencies
|
900
|
+
|
499
901
|
except Exception as e:
|
500
|
-
|
902
|
+
print_warning(f"Regional dependency scan failed for {vpc_id} in {region}: {e}")
|
903
|
+
return {"error": str(e)}
|
904
|
+
|
905
|
+
def _get_eni_count_regional(self, vpc_id: str, region: str) -> int:
|
906
|
+
"""Get ENI count for a VPC in a specific region (ENI Gate implementation)."""
|
907
|
+
try:
|
908
|
+
if self.session:
|
909
|
+
ec2 = self.session.client("ec2", region_name=region)
|
910
|
+
eni_response = ec2.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
911
|
+
return len(eni_response.get("NetworkInterfaces", []))
|
912
|
+
else:
|
913
|
+
return 0
|
914
|
+
except Exception as e:
|
915
|
+
print_warning(f"ENI count failed for {vpc_id} in {region}: {e}")
|
916
|
+
return -1 # Error indicator
|
917
|
+
|
918
|
+
def _apply_three_bucket_classification_enhanced(self, analysis_results: List[Dict[str, Any]]) -> Dict[str, Any]:
|
919
|
+
"""Apply Three-Bucket Cleanup Sequence classification with enhanced cost analysis."""
|
920
|
+
|
921
|
+
bucket_1_internal = [] # NAT Gateways, VPC Endpoints, Network Firewall
|
922
|
+
bucket_2_external = [] # VPC Peering, TGW/VPN, Internet Gateways
|
923
|
+
bucket_3_control = [] # Route 53, PHZ, RAM, Subnet Groups, Flow Logs
|
924
|
+
immediate_cleanup = [] # Zero ENI VPCs ready for immediate deletion
|
925
|
+
|
926
|
+
total_cleanup_savings = 0
|
927
|
+
|
928
|
+
for vpc in analysis_results:
|
929
|
+
vpc_id = vpc["vpc_id"]
|
930
|
+
deps = vpc.get("dependencies", {})
|
931
|
+
eni_count = vpc.get("eni_count", 0)
|
932
|
+
|
933
|
+
# Immediate cleanup candidates (Zero ENI Gate passed)
|
934
|
+
if eni_count == 0 and vpc.get("eni_gate_passed", False):
|
935
|
+
immediate_cleanup.append(
|
936
|
+
{
|
937
|
+
"vpc_id": vpc_id,
|
938
|
+
"vpc_name": vpc.get("vpc_name", "Unknown"),
|
939
|
+
"region": vpc.get("region", "unknown"),
|
940
|
+
"estimated_monthly_savings": vpc.get("estimated_monthly_cost", 0),
|
941
|
+
"cleanup_ready": True,
|
942
|
+
}
|
943
|
+
)
|
944
|
+
total_cleanup_savings += vpc.get("estimated_monthly_cost", 0)
|
945
|
+
continue
|
946
|
+
|
947
|
+
# Bucket 1: Internal Data Plane
|
948
|
+
if len(deps.get("nat_gateways", [])) > 0 or len(deps.get("vpc_endpoints", [])) > 0:
|
949
|
+
bucket_1_internal.append(
|
950
|
+
{
|
951
|
+
"vpc_id": vpc_id,
|
952
|
+
"nat_gateways": len(deps.get("nat_gateways", [])),
|
953
|
+
"vpc_endpoints": len(deps.get("vpc_endpoints", [])),
|
954
|
+
"estimated_savings": vpc.get("estimated_monthly_cost", 0) * 0.6, # 60% from internal cleanup
|
955
|
+
}
|
956
|
+
)
|
957
|
+
|
958
|
+
# Bucket 2: External Interconnects
|
959
|
+
if len(deps.get("vpc_peering", [])) > 0 or len(deps.get("internet_gateways", [])) > 0:
|
960
|
+
bucket_2_external.append(
|
961
|
+
{
|
962
|
+
"vpc_id": vpc_id,
|
963
|
+
"vpc_peering": len(deps.get("vpc_peering", [])),
|
964
|
+
"internet_gateways": len(deps.get("internet_gateways", [])),
|
965
|
+
"estimated_savings": vpc.get("estimated_monthly_cost", 0) * 0.3, # 30% from external cleanup
|
966
|
+
}
|
967
|
+
)
|
968
|
+
|
969
|
+
# Bucket 3: Control Plane (Route Tables, Security Groups)
|
970
|
+
if (
|
971
|
+
len(deps.get("route_tables", [])) > 2 # More than default
|
972
|
+
or len(deps.get("security_groups", [])) > 1
|
973
|
+
): # More than default
|
974
|
+
bucket_3_control.append(
|
975
|
+
{
|
976
|
+
"vpc_id": vpc_id,
|
977
|
+
"route_tables": len(deps.get("route_tables", [])),
|
978
|
+
"security_groups": len(deps.get("security_groups", [])),
|
979
|
+
"estimated_savings": vpc.get("estimated_monthly_cost", 0) * 0.1, # 10% from control cleanup
|
980
|
+
}
|
981
|
+
)
|
982
|
+
|
983
|
+
return {
|
984
|
+
"immediate_cleanup": {
|
985
|
+
"vpcs": immediate_cleanup,
|
986
|
+
"count": len(immediate_cleanup),
|
987
|
+
"monthly_savings": total_cleanup_savings,
|
988
|
+
"annual_savings": total_cleanup_savings * 12,
|
989
|
+
},
|
990
|
+
"bucket_1_internal": {
|
991
|
+
"vpcs": bucket_1_internal,
|
992
|
+
"count": len(bucket_1_internal),
|
993
|
+
"focus": "NAT Gateways, VPC Endpoints, Network Firewall",
|
994
|
+
},
|
995
|
+
"bucket_2_external": {
|
996
|
+
"vpcs": bucket_2_external,
|
997
|
+
"count": len(bucket_2_external),
|
998
|
+
"focus": "VPC Peering, TGW/VPN, Internet Gateways",
|
999
|
+
},
|
1000
|
+
"bucket_3_control": {
|
1001
|
+
"vpcs": bucket_3_control,
|
1002
|
+
"count": len(bucket_3_control),
|
1003
|
+
"focus": "Route 53, PHZ, RAM, Subnet Groups, Flow Logs",
|
1004
|
+
},
|
1005
|
+
"summary": {
|
1006
|
+
"total_vpcs_analyzed": len(analysis_results),
|
1007
|
+
"immediate_cleanup_ready": len(immediate_cleanup),
|
1008
|
+
"requires_three_bucket_process": len(bucket_1_internal)
|
1009
|
+
+ len(bucket_2_external)
|
1010
|
+
+ len(bucket_3_control),
|
1011
|
+
"estimated_annual_savings": total_cleanup_savings * 12,
|
1012
|
+
},
|
1013
|
+
}
|
1014
|
+
|
1015
|
+
def _estimate_vpc_cost(self, dependencies: Dict[str, Any], region: str) -> float:
|
1016
|
+
"""Estimate monthly cost savings from VPC cleanup based on dependencies."""
|
1017
|
+
|
1018
|
+
# AWS pricing estimates (monthly USD, varies by region)
|
1019
|
+
base_pricing = {
|
1020
|
+
"nat_gateway": 45.0, # $45/month per NAT Gateway
|
1021
|
+
"vpc_endpoint": 7.2, # $7.20/month per endpoint (720 hours)
|
1022
|
+
"internet_gateway": 0, # Free, but data transfer costs
|
1023
|
+
"elastic_ip": 3.6, # $3.60/month per unused EIP
|
1024
|
+
"vpc_base": 0, # VPC itself is free
|
1025
|
+
"data_transfer": 0.09, # $0.09/GB estimate for cleanup
|
1026
|
+
}
|
1027
|
+
|
1028
|
+
estimated_cost = 0
|
1029
|
+
|
1030
|
+
# NAT Gateway costs (major cost driver)
|
1031
|
+
nat_count = len(dependencies.get("nat_gateways", []))
|
1032
|
+
estimated_cost += nat_count * base_pricing["nat_gateway"]
|
1033
|
+
|
1034
|
+
# VPC Endpoint costs
|
1035
|
+
endpoint_count = len(dependencies.get("vpc_endpoints", []))
|
1036
|
+
estimated_cost += endpoint_count * base_pricing["vpc_endpoint"]
|
1037
|
+
|
1038
|
+
# Estimate unused Elastic IPs (simplified)
|
1039
|
+
estimated_cost += len(dependencies.get("enis", [])) * 0.1 * base_pricing["elastic_ip"]
|
1040
|
+
|
1041
|
+
# Base infrastructure overhead estimate
|
1042
|
+
if estimated_cost > 0:
|
1043
|
+
estimated_cost += 15.0 # Base infrastructure overhead
|
1044
|
+
|
1045
|
+
return round(estimated_cost, 2)
|
1046
|
+
|
1047
|
+
def _test_mode_vpc_analysis(self, vpc_ids: Optional[List[str]] = None) -> Dict[str, Any]:
|
1048
|
+
"""
|
1049
|
+
Test mode VPC analysis using production test data for validation.
|
1050
|
+
|
1051
|
+
Returns comprehensive analysis using 27 VPCs across 10 regions from test data.
|
1052
|
+
"""
|
1053
|
+
print_success("🧪 Test Mode: Analyzing VPCs using production test data")
|
1054
|
+
|
1055
|
+
active_vpcs = self.test_data_loader.get_active_vpcs()
|
1056
|
+
test_regions = self.test_data_loader.get_test_regions()
|
1057
|
+
business_metrics = self.test_data_loader.get_business_metrics()
|
1058
|
+
|
1059
|
+
# Filter VPCs if specific IDs requested
|
1060
|
+
if vpc_ids:
|
1061
|
+
active_vpcs = [vpc for vpc in active_vpcs if vpc["vpc_id"] in vpc_ids]
|
1062
|
+
|
1063
|
+
analysis_results = []
|
1064
|
+
region_summary = {}
|
1065
|
+
|
1066
|
+
print_success(f"🌍 Test mode multi-region analysis across {len(test_regions)} regions...")
|
1067
|
+
|
1068
|
+
for region in test_regions:
|
1069
|
+
region_vpcs = self.test_data_loader.get_vpcs_by_region(region)
|
1070
|
+
if not region_vpcs:
|
1071
|
+
continue
|
1072
|
+
|
1073
|
+
print_success(f"📍 Region {region}: Found {len(region_vpcs)} VPCs")
|
1074
|
+
region_summary[region] = len(region_vpcs)
|
1075
|
+
|
1076
|
+
for vpc_data in region_vpcs:
|
1077
|
+
# Convert test data to analysis format
|
1078
|
+
vpc_analysis = {
|
1079
|
+
"vpc_id": vpc_data["vpc_id"],
|
1080
|
+
"vpc_name": vpc_data["name"],
|
1081
|
+
"region": region,
|
1082
|
+
"is_default": vpc_data["name"].startswith("default"),
|
1083
|
+
"state": "available",
|
1084
|
+
"cidr_block": vpc_data["cidr"],
|
1085
|
+
"dependencies": self._simulate_dependencies_from_test_data(vpc_data),
|
1086
|
+
"eni_count": vpc_data["enis"],
|
1087
|
+
"eni_gate_passed": vpc_data["enis"] == 0,
|
1088
|
+
"total_dependencies": self._calculate_dependencies_from_test_data(vpc_data),
|
1089
|
+
"iac_managed": False, # Simplified for test mode
|
1090
|
+
"iac_sources": {},
|
1091
|
+
"cleanup_ready": vpc_data["enis"] == 0 and vpc_data.get("decision") == "DELETE",
|
1092
|
+
"safety_score": "SAFE" if vpc_data["enis"] == 0 else "REQUIRES_ANALYSIS",
|
1093
|
+
"blocking_factors": self._get_blocking_factors_from_test_data(vpc_data),
|
1094
|
+
"estimated_monthly_cost": vpc_data.get("cost_monthly", vpc_data.get("cost_annual", 0) / 12),
|
1095
|
+
"test_data_source": True,
|
1096
|
+
}
|
1097
|
+
|
1098
|
+
analysis_results.append(vpc_analysis)
|
1099
|
+
|
1100
|
+
print_success(f"✅ Test mode analysis complete: {len(analysis_results)} VPCs analyzed")
|
1101
|
+
|
1102
|
+
# Generate three-bucket classification from test data
|
1103
|
+
three_buckets = self._apply_three_bucket_classification_enhanced(analysis_results)
|
1104
|
+
|
1105
|
+
# Calculate financial metrics from test data
|
1106
|
+
total_potential_savings = sum(
|
1107
|
+
vpc.get("estimated_monthly_cost", 0) for vpc in analysis_results if vpc.get("cleanup_ready", False)
|
1108
|
+
)
|
1109
|
+
annual_savings = total_potential_savings * 12
|
1110
|
+
|
1111
|
+
return {
|
1112
|
+
"source": "test_mode_vpc_analysis",
|
1113
|
+
"test_data_path": self.test_data_loader.test_data_path,
|
1114
|
+
"total_vpcs_analyzed": len(analysis_results),
|
1115
|
+
"regions_scanned": len(test_regions),
|
1116
|
+
"regions_with_vpcs": len(region_summary),
|
1117
|
+
"region_summary": region_summary,
|
1118
|
+
"vpc_analysis": analysis_results,
|
1119
|
+
"three_bucket_classification": three_buckets,
|
1120
|
+
"financial_analysis": {
|
1121
|
+
"total_monthly_cost_at_risk": total_potential_savings,
|
1122
|
+
"annual_savings_potential": annual_savings,
|
1123
|
+
"target_annual_savings": business_metrics.get("annual_savings", 0),
|
1124
|
+
"cleanup_ready_vpcs": len([vpc for vpc in analysis_results if vpc.get("cleanup_ready", False)]),
|
1125
|
+
"requires_analysis_vpcs": len([vpc for vpc in analysis_results if not vpc.get("cleanup_ready", False)]),
|
1126
|
+
},
|
1127
|
+
"business_validation": {
|
1128
|
+
"exceeds_target": annual_savings >= business_metrics.get("annual_savings", 0),
|
1129
|
+
"target_achievement_percentage": round(
|
1130
|
+
(annual_savings / business_metrics.get("annual_savings", 1)) * 100, 1
|
1131
|
+
)
|
1132
|
+
if business_metrics.get("annual_savings", 0) > 0
|
1133
|
+
else 0,
|
1134
|
+
},
|
1135
|
+
"test_metadata": {
|
1136
|
+
"total_test_vpcs": business_metrics.get("total_vpcs", 0),
|
1137
|
+
"active_vpcs": business_metrics.get("active_vpcs", 0),
|
1138
|
+
"deleted_vpcs": business_metrics.get("deleted_vpcs", 0),
|
1139
|
+
},
|
1140
|
+
"mcp_validated": False,
|
1141
|
+
"test_mode": True,
|
1142
|
+
"accuracy_note": "Test mode analysis - use real AWS profile for production validation",
|
1143
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
1144
|
+
}
|
1145
|
+
|
1146
|
+
def _simulate_dependencies_from_test_data(self, vpc_data: Dict[str, Any]) -> Dict[str, List]:
|
1147
|
+
"""Simulate VPC dependencies based on test data characteristics."""
|
1148
|
+
dependencies = {
|
1149
|
+
"enis": [],
|
1150
|
+
"subnets": [],
|
1151
|
+
"security_groups": [],
|
1152
|
+
"route_tables": [],
|
1153
|
+
"nat_gateways": [],
|
1154
|
+
"internet_gateways": [],
|
1155
|
+
"vpc_endpoints": [],
|
1156
|
+
"vpc_peering": [],
|
1157
|
+
"vpn_gateways": [],
|
1158
|
+
"vpn_connections": [],
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
# Simulate ENIs based on count in test data
|
1162
|
+
eni_count = vpc_data.get("enis", 0)
|
1163
|
+
dependencies["enis"] = [f"eni-{vpc_data['vpc_id'][-8:]}{i:02d}" for i in range(eni_count)]
|
1164
|
+
|
1165
|
+
# Simulate basic dependencies for active VPCs
|
1166
|
+
if vpc_data.get("decision") != "DELETE":
|
1167
|
+
dependencies["subnets"] = [f"subnet-{vpc_data['vpc_id'][-8:]}001", f"subnet-{vpc_data['vpc_id'][-8:]}002"]
|
1168
|
+
dependencies["security_groups"] = [f"sg-{vpc_data['vpc_id'][-8:]}default"]
|
1169
|
+
dependencies["route_tables"] = [f"rtb-{vpc_data['vpc_id'][-8:]}main"]
|
1170
|
+
|
1171
|
+
# Add NAT gateway for higher cost VPCs
|
1172
|
+
if vpc_data.get("cost_monthly", 0) > 100:
|
1173
|
+
dependencies["nat_gateways"] = [f"nat-{vpc_data['vpc_id'][-8:]}"]
|
1174
|
+
|
1175
|
+
# Add internet gateway for non-private VPCs
|
1176
|
+
if not vpc_data.get("name", "").endswith("-private"):
|
1177
|
+
dependencies["internet_gateways"] = [f"igw-{vpc_data['vpc_id'][-8:]}"]
|
1178
|
+
|
1179
|
+
return dependencies
|
1180
|
+
|
1181
|
+
def _calculate_dependencies_from_test_data(self, vpc_data: Dict[str, Any]) -> int:
|
1182
|
+
"""Calculate total dependency count from test data."""
|
1183
|
+
# Base dependencies for any VPC
|
1184
|
+
base_deps = 2 # Route table + security group
|
1185
|
+
|
1186
|
+
# Add ENI count
|
1187
|
+
base_deps += vpc_data.get("enis", 0)
|
1188
|
+
|
1189
|
+
# Add more dependencies for complex VPCs
|
1190
|
+
if vpc_data.get("cost_monthly", 0) > 100:
|
1191
|
+
base_deps += 3 # NAT gateway + subnets + internet gateway
|
1192
|
+
|
1193
|
+
return base_deps
|
1194
|
+
|
1195
|
+
def _get_blocking_factors_from_test_data(self, vpc_data: Dict[str, Any]) -> List[str]:
|
1196
|
+
"""Get blocking factors from test data characteristics."""
|
1197
|
+
blocking_factors = []
|
1198
|
+
|
1199
|
+
eni_count = vpc_data.get("enis", 0)
|
1200
|
+
if eni_count > 0:
|
1201
|
+
blocking_factors.append(f"{eni_count} network interfaces attached")
|
1202
|
+
|
1203
|
+
if vpc_data.get("cost_monthly", 0) > 100:
|
1204
|
+
blocking_factors.append("High-cost infrastructure requires analysis")
|
1205
|
+
|
1206
|
+
if vpc_data.get("name", "").startswith("default"):
|
1207
|
+
blocking_factors.append("Default VPC (CIS 2.1 compliance review required)")
|
1208
|
+
|
1209
|
+
decision = vpc_data.get("decision", "")
|
1210
|
+
if decision == "KEEP":
|
1211
|
+
blocking_factors.append("Marked for retention (business critical)")
|
1212
|
+
elif decision == "OPTIMIZE":
|
1213
|
+
blocking_factors.append("Optimization candidate (cost reduction potential)")
|
1214
|
+
|
1215
|
+
if not blocking_factors:
|
1216
|
+
blocking_factors.append("None - ready for cleanup")
|
1217
|
+
|
1218
|
+
return blocking_factors
|
1219
|
+
|
1220
|
+
# ==========================================
|
1221
|
+
# Cost Explorer MCP Integration Methods
|
1222
|
+
# ==========================================
|
1223
|
+
|
1224
|
+
def validate_vpc_cost_projections_with_mcp(
|
1225
|
+
self, vpc_cost_data: Optional[List[Dict[str, Any]]] = None, target_savings: float = 7548
|
1226
|
+
) -> Dict[str, Any]:
|
1227
|
+
"""
|
1228
|
+
Validate VPC cost projections using Cost Explorer MCP integration.
|
1229
|
+
|
1230
|
+
Phase 2 critical implementation: Validates $7,548+ annual savings target
|
1231
|
+
with ≥99.5% accuracy requirement using real AWS Cost Explorer data.
|
1232
|
+
|
1233
|
+
Args:
|
1234
|
+
vpc_cost_data: VPC cost data for validation (uses test data if None)
|
1235
|
+
target_savings: Target annual savings (default: $7,548)
|
1236
|
+
|
1237
|
+
Returns:
|
1238
|
+
Comprehensive cost validation with enterprise accuracy requirements
|
1239
|
+
"""
|
1240
|
+
print_success("💰 Phase 2: Cost Explorer MCP Integration - Financial Validation")
|
1241
|
+
|
1242
|
+
# Use test data if vpc_cost_data not provided
|
1243
|
+
if vpc_cost_data is None:
|
1244
|
+
if self.test_mode and self.test_data_loader:
|
1245
|
+
vpc_cost_data = self._extract_cost_data_from_test_data()
|
1246
|
+
else:
|
1247
|
+
return {"error": "No VPC cost data available for validation"}
|
1248
|
+
|
1249
|
+
if self.have_runbooks and hasattr(self, "cost_explorer_mcp"):
|
1250
|
+
try:
|
1251
|
+
# Phase 2: Cost Explorer MCP validation
|
1252
|
+
cost_validation_results = self.cost_explorer_mcp.validate_vpc_cost_projections(
|
1253
|
+
vpc_cost_data=vpc_cost_data, validation_period_days=90
|
1254
|
+
)
|
1255
|
+
|
1256
|
+
# Generate executive report
|
1257
|
+
executive_report = self.cost_explorer_mcp.generate_cost_validation_report(
|
1258
|
+
cost_validation_results, target_savings=target_savings
|
1259
|
+
)
|
1260
|
+
|
1261
|
+
return {
|
1262
|
+
"source": "cost_explorer_mcp_integration",
|
1263
|
+
"phase": "Phase 2: Cost Explorer MCP Validation",
|
1264
|
+
"cost_validation": cost_validation_results,
|
1265
|
+
"executive_report": executive_report,
|
1266
|
+
"target_savings": target_savings,
|
1267
|
+
"accuracy_achieved": cost_validation_results.get("accuracy_score", 0),
|
1268
|
+
"accuracy_requirement": 99.5,
|
1269
|
+
"validation_passed": cost_validation_results.get("validation_passed", False),
|
1270
|
+
"business_readiness": executive_report.get("executive_summary", {}).get(
|
1271
|
+
"business_readiness", False
|
1272
|
+
),
|
1273
|
+
"enterprise_coordination": "sre_automation_specialist_phase_2_complete",
|
1274
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
1275
|
+
}
|
1276
|
+
|
1277
|
+
except Exception as e:
|
1278
|
+
print_error(f"Cost Explorer MCP validation failed: {e}")
|
1279
|
+
return self._fallback_cost_validation_summary(vpc_cost_data, target_savings)
|
1280
|
+
|
1281
|
+
# Fallback validation if Cost Explorer MCP unavailable
|
1282
|
+
return self._fallback_cost_validation_summary(vpc_cost_data, target_savings)
|
1283
|
+
|
1284
|
+
def validate_test_data_business_metrics_with_mcp(self) -> Dict[str, Any]:
|
1285
|
+
"""
|
1286
|
+
Validate business metrics from vpc-test-data-production.yaml using Cost Explorer MCP.
|
1287
|
+
|
1288
|
+
Validates the $11,070 annual savings target against real AWS billing data
|
1289
|
+
with ≥99.5% accuracy requirement for Phase 2 completion.
|
1290
|
+
"""
|
1291
|
+
print_success("🧪 Validating test data business metrics with Cost Explorer MCP")
|
1292
|
+
|
1293
|
+
if not self.test_mode:
|
1294
|
+
return {"error": "Test mode not available - no test data loaded"}
|
1295
|
+
|
1296
|
+
if self.have_runbooks and hasattr(self, "cost_explorer_mcp"):
|
1297
|
+
try:
|
1298
|
+
# Validate business metrics from test data
|
1299
|
+
test_data_path = str(self.test_data_loader.test_data_path)
|
1300
|
+
business_validation = self.cost_explorer_mcp.validate_test_data_business_metrics(test_data_path)
|
1301
|
+
|
1302
|
+
# Check if validation meets Phase 2 requirements
|
1303
|
+
accuracy = business_validation.get("test_data_validation", {}).get("validation_accuracy", 0)
|
1304
|
+
validation_passed = accuracy >= 99.5
|
1305
|
+
exceeds_target = business_validation.get("test_data_validation", {}).get("exceeds_target_7548", False)
|
1306
|
+
|
1307
|
+
return {
|
1308
|
+
"source": "cost_explorer_mcp_business_validation",
|
1309
|
+
"phase_2_status": "COMPLETE" if validation_passed and exceeds_target else "REQUIRES_REVIEW",
|
1310
|
+
"business_validation": business_validation,
|
1311
|
+
"accuracy_achieved": accuracy,
|
1312
|
+
"accuracy_requirement": 99.5,
|
1313
|
+
"meets_accuracy_requirement": validation_passed,
|
1314
|
+
"exceeds_7548_target": exceeds_target,
|
1315
|
+
"test_data_source": test_data_path,
|
1316
|
+
"enterprise_coordination": "sre_automation_specialist_business_metrics_validated",
|
1317
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
1318
|
+
}
|
1319
|
+
|
1320
|
+
except Exception as e:
|
1321
|
+
print_error(f"Business metrics validation failed: {e}")
|
1322
|
+
return {"error": f"Business metrics MCP validation failed: {str(e)}"}
|
1323
|
+
|
1324
|
+
# Fallback validation without MCP
|
1325
|
+
return self._fallback_business_metrics_validation()
|
1326
|
+
|
1327
|
+
def _extract_cost_data_from_test_data(self) -> List[Dict[str, Any]]:
|
1328
|
+
"""Extract cost data from test data for MCP validation."""
|
1329
|
+
if not self.test_data_loader or not self.test_mode:
|
1330
|
+
return []
|
1331
|
+
|
1332
|
+
# Get active VPCs from test data
|
1333
|
+
active_vpcs = self.test_data_loader.get_active_vpcs()
|
1334
|
+
|
1335
|
+
# Convert to cost validation format
|
1336
|
+
cost_data = []
|
1337
|
+
for vpc in active_vpcs:
|
1338
|
+
cost_entry = {
|
1339
|
+
"vpc_id": vpc.get("vpc_id", "unknown"),
|
1340
|
+
"name": vpc.get("name", "Unknown"),
|
1341
|
+
"region": vpc.get("region", "unknown"),
|
1342
|
+
"cost_monthly": vpc.get("cost_monthly", 0),
|
1343
|
+
"cost_annual": vpc.get("cost_annual", vpc.get("cost_monthly", 0) * 12),
|
1344
|
+
"decision": vpc.get("decision", "ANALYZE"),
|
1345
|
+
"cleanup_priority": vpc.get("cleanup_priority", "MEDIUM"),
|
1346
|
+
}
|
1347
|
+
cost_data.append(cost_entry)
|
1348
|
+
|
1349
|
+
return cost_data
|
1350
|
+
|
1351
|
+
def _fallback_cost_validation_summary(
|
1352
|
+
self, vpc_cost_data: List[Dict[str, Any]], target_savings: float
|
1353
|
+
) -> Dict[str, Any]:
|
1354
|
+
"""Fallback cost validation summary when MCP unavailable."""
|
1355
|
+
total_projected_savings = sum(vpc.get("cost_annual", vpc.get("cost_monthly", 0) * 12) for vpc in vpc_cost_data)
|
1356
|
+
|
1357
|
+
return {
|
1358
|
+
"source": "fallback_cost_validation_summary",
|
1359
|
+
"total_projected_savings": total_projected_savings,
|
1360
|
+
"target_savings": target_savings,
|
1361
|
+
"exceeds_target": total_projected_savings >= target_savings,
|
1362
|
+
"accuracy_score": 85.0, # Conservative fallback accuracy
|
1363
|
+
"validation_passed": False, # Cannot pass without MCP
|
1364
|
+
"limitation": "Cost Explorer MCP integration required for ≥99.5% accuracy",
|
1365
|
+
"recommendation": "Enable Cost Explorer MCP server for Phase 2 completion",
|
1366
|
+
"next_steps": [
|
1367
|
+
"Configure Cost Explorer MCP server",
|
1368
|
+
"Validate BILLING_PROFILE access",
|
1369
|
+
"Retry with MCP integration",
|
1370
|
+
],
|
1371
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
1372
|
+
}
|
1373
|
+
|
1374
|
+
def _fallback_business_metrics_validation(self) -> Dict[str, Any]:
|
1375
|
+
"""Fallback business metrics validation without MCP."""
|
1376
|
+
business_metrics = self.test_data_loader.get_business_metrics() if self.test_data_loader else {}
|
1377
|
+
|
1378
|
+
return {
|
1379
|
+
"source": "fallback_business_metrics_validation",
|
1380
|
+
"business_metrics": business_metrics,
|
1381
|
+
"annual_savings": business_metrics.get("annual_savings", 0),
|
1382
|
+
"validation_accuracy": 85.0, # Conservative fallback
|
1383
|
+
"meets_accuracy_requirement": False,
|
1384
|
+
"limitation": "Cost Explorer MCP integration required",
|
1385
|
+
"recommendation": "Enable MCP integration for accurate business validation",
|
1386
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
def validate_test_mode_accuracy(self) -> Dict[str, Any]:
|
1390
|
+
"""Validate test mode implementation accuracy against expected business metrics."""
|
1391
|
+
if not self.test_mode:
|
1392
|
+
return {"error": "Test mode not available - no test data loaded"}
|
1393
|
+
|
1394
|
+
print_success("🧪 Validating test mode accuracy against business metrics...")
|
1395
|
+
|
1396
|
+
# Run test mode analysis
|
1397
|
+
test_results = self._test_mode_vpc_analysis()
|
1398
|
+
|
1399
|
+
business_metrics = self.test_data_loader.get_business_metrics()
|
1400
|
+
expected_savings = business_metrics.get("annual_savings", 0)
|
1401
|
+
actual_savings = test_results["financial_analysis"]["annual_savings_potential"]
|
1402
|
+
|
1403
|
+
# Validation metrics
|
1404
|
+
savings_accuracy = (actual_savings / expected_savings * 100) if expected_savings > 0 else 0
|
1405
|
+
vpc_count_accuracy = test_results["total_vpcs_analyzed"] / business_metrics.get("total_vpcs", 1) * 100
|
1406
|
+
|
1407
|
+
validation_passed = (
|
1408
|
+
abs(savings_accuracy - 100) <= 10 # Within 10% of expected savings
|
1409
|
+
and vpc_count_accuracy >= 90 # At least 90% of VPCs analyzed
|
1410
|
+
)
|
1411
|
+
|
1412
|
+
return {
|
1413
|
+
"validation_passed": validation_passed,
|
1414
|
+
"savings_accuracy_percentage": round(savings_accuracy, 1),
|
1415
|
+
"vpc_count_accuracy_percentage": round(vpc_count_accuracy, 1),
|
1416
|
+
"expected_annual_savings": expected_savings,
|
1417
|
+
"actual_annual_savings": round(actual_savings, 2),
|
1418
|
+
"expected_vpc_count": business_metrics.get("total_vpcs", 0),
|
1419
|
+
"actual_vpc_count": test_results["total_vpcs_analyzed"],
|
1420
|
+
"test_regions": len(test_results["region_summary"]),
|
1421
|
+
"cleanup_ready_vpcs": test_results["financial_analysis"]["cleanup_ready_vpcs"],
|
1422
|
+
"validation_timestamp": datetime.now(timezone.utc).isoformat(),
|
1423
|
+
"recommendation": "Test mode validation complete - ready for real AWS profile testing"
|
1424
|
+
if validation_passed
|
1425
|
+
else "Test mode needs adjustment - check business metrics alignment",
|
1426
|
+
}
|