runbooks 1.1.3__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/WEIGHT_CONFIG_README.md +1 -1
- runbooks/cfat/assessment/compliance.py +8 -8
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cfat/models.py +6 -2
- runbooks/cfat/tests/__init__.py +6 -1
- runbooks/cli/__init__.py +13 -0
- runbooks/cli/commands/cfat.py +274 -0
- runbooks/cli/commands/finops.py +1164 -0
- runbooks/cli/commands/inventory.py +379 -0
- runbooks/cli/commands/operate.py +239 -0
- runbooks/cli/commands/security.py +248 -0
- runbooks/cli/commands/validation.py +825 -0
- runbooks/cli/commands/vpc.py +310 -0
- runbooks/cli/registry.py +107 -0
- runbooks/cloudops/__init__.py +23 -30
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +549 -547
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +226 -227
- 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 +179 -215
- 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 +341 -0
- runbooks/common/aws_utils.py +75 -80
- runbooks/common/business_logic.py +127 -105
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
- runbooks/common/cross_account_manager.py +198 -205
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +235 -0
- 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 +478 -495
- 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 +176 -194
- runbooks/common/patterns.py +204 -0
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +248 -39
- runbooks/common/rich_utils.py +643 -92
- 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 +29 -33
- 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 +488 -622
- 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 +40 -37
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +230 -292
- 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 +338 -175
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1513 -482
- 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 +25 -29
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +77 -78
- runbooks/finops/scenarios.py +1278 -439
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/tests/test_finops_dashboard.py +3 -3
- runbooks/finops/tests/test_reference_images_validation.py +2 -2
- runbooks/finops/tests/test_single_account_features.py +17 -17
- runbooks/finops/tests/validate_test_suite.py +1 -1
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +263 -269
- runbooks/finops/vpc_cleanup_exporter.py +191 -146
- runbooks/finops/vpc_cleanup_optimizer.py +593 -575
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/hitl/enhanced_workflow_engine.py +1 -1
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/README.md +3 -3
- runbooks/inventory/Tests/common_test_data.py +30 -30
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +28 -11
- runbooks/inventory/collectors/aws_networking.py +111 -101
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/discovery.md +2 -2
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/find_ec2_security_groups.py +1 -1
- 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 +56 -52
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +733 -696
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +3 -3
- runbooks/main.py +152 -9147
- 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/metrics/dora_metrics_engine.py +2 -2
- 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/mcp_integration.py +1 -1
- runbooks/operate/networking_cost_heatmap.py +33 -10
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/operate/vpc_operations.py +648 -618
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +71 -67
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +91 -65
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +49 -44
- 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/integration_test_enterprise_security.py +5 -3
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/run_script.py +1 -1
- 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/mcp_reliability_engine.py +6 -6
- 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 +51 -48
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +754 -708
- 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 +190 -162
- runbooks/vpc/mcp_no_eni_validator.py +681 -640
- 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 +1302 -1129
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
- 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 -956
- 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.3.dist-info/METADATA +0 -799
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -73,8 +73,14 @@ from botocore.exceptions import ClientError
|
|
73
73
|
from loguru import logger
|
74
74
|
|
75
75
|
from runbooks.common.rich_utils import (
|
76
|
-
console,
|
77
|
-
|
76
|
+
console,
|
77
|
+
create_table,
|
78
|
+
print_warning,
|
79
|
+
print_success,
|
80
|
+
print_error,
|
81
|
+
print_info,
|
82
|
+
print_header,
|
83
|
+
create_panel,
|
78
84
|
)
|
79
85
|
from runbooks.operate.base import BaseOperation, OperationContext, OperationResult, OperationStatus
|
80
86
|
|
@@ -84,13 +90,13 @@ from runbooks.inventory.vpc_analyzer import VPCAnalyzer, VPCDiscoveryResult, AWS
|
|
84
90
|
|
85
91
|
class RichConsoleWrapper:
|
86
92
|
"""Wrapper to provide missing methods for VPC operations."""
|
87
|
-
|
93
|
+
|
88
94
|
def __init__(self, console_instance):
|
89
95
|
self.console = console_instance
|
90
|
-
|
96
|
+
|
91
97
|
def print(self, *args, **kwargs):
|
92
98
|
return self.console.print(*args, **kwargs)
|
93
|
-
|
99
|
+
|
94
100
|
def print_panel(self, content, subtitle=None, title="Panel", style=None):
|
95
101
|
"""Print a panel with content."""
|
96
102
|
panel_content = content
|
@@ -98,23 +104,23 @@ class RichConsoleWrapper:
|
|
98
104
|
panel_content = f"{content}\n\n{subtitle}"
|
99
105
|
panel = create_panel(panel_content, title=title)
|
100
106
|
self.console.print(panel)
|
101
|
-
|
107
|
+
|
102
108
|
def print_table(self, table):
|
103
109
|
"""Print a table."""
|
104
110
|
self.console.print(table)
|
105
|
-
|
111
|
+
|
106
112
|
def print_success(self, message):
|
107
113
|
return print_success(message)
|
108
|
-
|
114
|
+
|
109
115
|
def print_error(self, message):
|
110
116
|
return print_error(message)
|
111
|
-
|
117
|
+
|
112
118
|
def print_info(self, message):
|
113
119
|
return print_info(message)
|
114
|
-
|
120
|
+
|
115
121
|
def print_warning(self, message):
|
116
122
|
return print_warning(message)
|
117
|
-
|
123
|
+
|
118
124
|
def print_header(self, title, subtitle=None):
|
119
125
|
"""Print header with Rich CLI standards - convert subtitle to version parameter."""
|
120
126
|
if subtitle:
|
@@ -167,6 +173,7 @@ class NATGatewayConfiguration:
|
|
167
173
|
if "MonthlyCostEstimate" not in self.tags:
|
168
174
|
try:
|
169
175
|
from ..common.aws_pricing import get_service_monthly_cost
|
176
|
+
|
170
177
|
monthly_cost = get_service_monthly_cost("nat_gateway", "us-east-1") # Default region
|
171
178
|
self.tags["MonthlyCostEstimate"] = f"${monthly_cost:.2f}"
|
172
179
|
except Exception:
|
@@ -260,35 +267,32 @@ class VPCOperations(BaseOperation):
|
|
260
267
|
self.rich_console = RichConsoleWrapper(console)
|
261
268
|
|
262
269
|
# VPC Module Migration Integration - Discovery capabilities
|
263
|
-
self.vpc_analyzer = VPCAnalyzer(
|
264
|
-
profile=profile,
|
265
|
-
region=region,
|
266
|
-
console=console,
|
267
|
-
dry_run=dry_run
|
268
|
-
)
|
270
|
+
self.vpc_analyzer = VPCAnalyzer(profile=profile, region=region, console=console, dry_run=dry_run)
|
269
271
|
|
270
272
|
# Cost tracking using enhanced AWS pricing API with enterprise fallback
|
271
273
|
import os
|
272
|
-
|
273
|
-
|
274
|
+
|
275
|
+
os.environ["AWS_PRICING_STRICT_COMPLIANCE"] = os.getenv("AWS_PRICING_STRICT_COMPLIANCE", "false")
|
276
|
+
|
274
277
|
try:
|
275
278
|
from ..common.aws_pricing_api import pricing_api
|
276
|
-
|
279
|
+
|
277
280
|
# Get dynamic pricing with enhanced fallback support
|
278
|
-
current_region = region or os.getenv(
|
279
|
-
|
281
|
+
current_region = region or os.getenv("AWS_DEFAULT_REGION", "us-east-1")
|
282
|
+
|
280
283
|
self.nat_gateway_monthly_cost = pricing_api.get_nat_gateway_monthly_cost(current_region)
|
281
284
|
logger.info(f"✅ Dynamic NAT Gateway cost: ${self.nat_gateway_monthly_cost:.2f}/month")
|
282
|
-
|
285
|
+
|
283
286
|
# Elastic IP pricing (using NAT Gateway as proxy for network pricing)
|
284
287
|
self.elastic_ip_monthly_cost = self.nat_gateway_monthly_cost * 0.1 # EIP typically 10% of NAT Gateway
|
285
288
|
logger.info(f"✅ Dynamic Elastic IP cost: ${self.elastic_ip_monthly_cost:.2f}/month")
|
286
|
-
|
289
|
+
|
287
290
|
except Exception as e:
|
288
291
|
logger.warning(f"⚠️ Enhanced pricing fallback: {e}")
|
289
292
|
# Use config-based pricing as ultimate fallback
|
290
293
|
try:
|
291
294
|
from ..vpc.config import load_config
|
295
|
+
|
292
296
|
vpc_config = load_config()
|
293
297
|
self.nat_gateway_monthly_cost = vpc_config.cost_model.nat_gateway_monthly
|
294
298
|
self.elastic_ip_monthly_cost = vpc_config.cost_model.elastic_ip_idle_monthly
|
@@ -296,13 +300,17 @@ class VPCOperations(BaseOperation):
|
|
296
300
|
logger.info(f"✅ Config-based Elastic IP cost: ${self.elastic_ip_monthly_cost:.2f}/month")
|
297
301
|
except Exception as config_error:
|
298
302
|
logger.error(f"🚫 All pricing methods failed: {config_error}")
|
299
|
-
raise RuntimeError(
|
300
|
-
|
303
|
+
raise RuntimeError(
|
304
|
+
"Unable to get pricing for VPC analysis. Check AWS credentials and IAM permissions."
|
305
|
+
) from config_error
|
306
|
+
|
301
307
|
# VPC module patterns integration
|
302
308
|
self.last_discovery_result = None
|
303
309
|
self.last_awso_analysis = None
|
304
310
|
|
305
|
-
logger.info(
|
311
|
+
logger.info(
|
312
|
+
f"VPC Operations initialized with VPC Analyzer - Profile: {profile}, Region: {region}, Dry-run: {dry_run}"
|
313
|
+
)
|
306
314
|
|
307
315
|
def execute_operation(self, context: OperationContext, operation_type: str, **kwargs) -> List[OperationResult]:
|
308
316
|
"""
|
@@ -378,7 +386,7 @@ class VPCOperations(BaseOperation):
|
|
378
386
|
f"Region: {context.region}\n"
|
379
387
|
f"DNS Hostnames: {vpc_config.enable_dns_hostnames}\n"
|
380
388
|
f"Instance Tenancy: {vpc_config.instance_tenancy}",
|
381
|
-
title="🏗️ VPC Creation"
|
389
|
+
title="🏗️ VPC Creation",
|
382
390
|
)
|
383
391
|
|
384
392
|
if context.dry_run:
|
@@ -862,30 +870,28 @@ class VPCOperations(BaseOperation):
|
|
862
870
|
def discover_vpc_topology_comprehensive(self, vpc_ids: Optional[List[str]] = None) -> VPCDiscoveryResult:
|
863
871
|
"""
|
864
872
|
Comprehensive VPC topology discovery using migrated VPC module capabilities.
|
865
|
-
|
873
|
+
|
866
874
|
Integrates networking_wrapper.py discovery patterns with operate module operations.
|
867
875
|
Provides complete VPC topology analysis for AWSO-05 cleanup workflows.
|
868
|
-
|
876
|
+
|
869
877
|
Args:
|
870
878
|
vpc_ids: Optional list of specific VPC IDs to analyze
|
871
|
-
|
879
|
+
|
872
880
|
Returns:
|
873
881
|
VPCDiscoveryResult with complete topology information
|
874
882
|
"""
|
875
883
|
self.rich_console.print_header("VPC Topology Discovery", "Enhanced with VPC Module Integration")
|
876
|
-
|
884
|
+
|
877
885
|
try:
|
878
886
|
# Use integrated VPC analyzer for discovery
|
879
887
|
discovery_result = self.vpc_analyzer.discover_vpc_topology(vpc_ids)
|
880
888
|
self.last_discovery_result = discovery_result
|
881
|
-
|
889
|
+
|
882
890
|
# Display enterprise summary with cost information
|
883
|
-
total_monthly_cost = sum([
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
])
|
888
|
-
|
891
|
+
total_monthly_cost = sum([nat["EstimatedMonthlyCost"] for nat in discovery_result.nat_gateways]) + sum(
|
892
|
+
[ep["EstimatedMonthlyCost"] for ep in discovery_result.vpc_endpoints]
|
893
|
+
)
|
894
|
+
|
889
895
|
self.rich_console.print_panel(
|
890
896
|
"VPC Discovery Integration Complete",
|
891
897
|
f"Total VPCs: {len(discovery_result.vpcs)}\n"
|
@@ -894,53 +900,55 @@ class VPCOperations(BaseOperation):
|
|
894
900
|
f"Network Interfaces: {len(discovery_result.network_interfaces)}\n"
|
895
901
|
f"Estimated Monthly Network Cost: ${total_monthly_cost:.2f}\n"
|
896
902
|
f"Discovery Timestamp: {discovery_result.discovery_timestamp}",
|
897
|
-
title="🔍 VPC Module Integration"
|
903
|
+
title="🔍 VPC Module Integration",
|
898
904
|
)
|
899
|
-
|
905
|
+
|
900
906
|
return discovery_result
|
901
|
-
|
907
|
+
|
902
908
|
except Exception as e:
|
903
909
|
logger.error(f"VPC topology discovery failed: {e}")
|
904
910
|
self.rich_console.print_error(f"❌ VPC discovery failed: {e}")
|
905
911
|
raise
|
906
912
|
|
907
|
-
def analyze_awso_dependencies_comprehensive(
|
913
|
+
def analyze_awso_dependencies_comprehensive(
|
914
|
+
self, discovery_result: Optional[VPCDiscoveryResult] = None
|
915
|
+
) -> AWSOAnalysis:
|
908
916
|
"""
|
909
917
|
AWSO-05 dependency analysis using migrated VPC module capabilities.
|
910
|
-
|
918
|
+
|
911
919
|
Integrates cost_engine.py analysis patterns with AWSO-05 cleanup requirements.
|
912
920
|
Provides 12-step dependency validation for safe VPC cleanup operations.
|
913
|
-
|
921
|
+
|
914
922
|
Args:
|
915
923
|
discovery_result: Previous discovery result (uses last if None)
|
916
|
-
|
924
|
+
|
917
925
|
Returns:
|
918
926
|
AWSOAnalysis with comprehensive dependency mapping
|
919
927
|
"""
|
920
928
|
self.rich_console.print_header("AWSO-05 Dependency Analysis", "12-Step Framework Integration")
|
921
|
-
|
929
|
+
|
922
930
|
try:
|
923
931
|
# Use integrated VPC analyzer for AWSO analysis
|
924
932
|
awso_analysis = self.vpc_analyzer.analyze_awso_dependencies(discovery_result)
|
925
933
|
self.last_awso_analysis = awso_analysis
|
926
|
-
|
934
|
+
|
927
935
|
# Display business-critical warnings
|
928
936
|
if awso_analysis.eni_gate_warnings:
|
929
937
|
self.rich_console.print_warning(
|
930
938
|
f"🚨 CRITICAL: {len(awso_analysis.eni_gate_warnings)} ENI gate warnings detected!\n"
|
931
939
|
"VPC cleanup may disrupt active workloads. Review migration requirements."
|
932
940
|
)
|
933
|
-
|
941
|
+
|
934
942
|
if awso_analysis.default_vpcs:
|
935
943
|
self.rich_console.print_info(
|
936
944
|
f"🎯 Default VPCs found: {len(awso_analysis.default_vpcs)}\n"
|
937
945
|
"CIS Benchmark compliance can be improved through cleanup."
|
938
946
|
)
|
939
|
-
|
947
|
+
|
940
948
|
# Display cleanup readiness status
|
941
|
-
cleanup_status = awso_analysis.evidence_bundle.get(
|
942
|
-
status_style = "green" if cleanup_status ==
|
943
|
-
|
949
|
+
cleanup_status = awso_analysis.evidence_bundle.get("CleanupReadiness", "UNKNOWN")
|
950
|
+
status_style = "green" if cleanup_status == "READY" else "yellow"
|
951
|
+
|
944
952
|
self.rich_console.print_panel(
|
945
953
|
"AWSO-05 Analysis Complete",
|
946
954
|
f"Default VPCs: {len(awso_analysis.default_vpcs)}\n"
|
@@ -948,11 +956,11 @@ class VPCOperations(BaseOperation):
|
|
948
956
|
f"Cleanup Recommendations: {len(awso_analysis.cleanup_recommendations)}\n"
|
949
957
|
f"Cleanup Readiness: {cleanup_status}",
|
950
958
|
title="🎯 AWSO-05 Analysis Results",
|
951
|
-
style=status_style
|
959
|
+
style=status_style,
|
952
960
|
)
|
953
|
-
|
961
|
+
|
954
962
|
return awso_analysis
|
955
|
-
|
963
|
+
|
956
964
|
except Exception as e:
|
957
965
|
logger.error(f"AWSO-05 analysis failed: {e}")
|
958
966
|
self.rich_console.print_error(f"❌ AWSO-05 analysis failed: {e}")
|
@@ -961,106 +969,111 @@ class VPCOperations(BaseOperation):
|
|
961
969
|
def generate_vpc_evidence_bundle(self, output_dir: str = "./awso_evidence") -> Dict[str, str]:
|
962
970
|
"""
|
963
971
|
Generate comprehensive evidence bundle using migrated VPC module capabilities.
|
964
|
-
|
972
|
+
|
965
973
|
Integrates manager_interface.py reporting patterns with AWSO-05 compliance requirements.
|
966
974
|
Creates SHA256-verified evidence bundle for audit trails and compliance.
|
967
|
-
|
975
|
+
|
968
976
|
Args:
|
969
977
|
output_dir: Directory to store evidence files
|
970
|
-
|
978
|
+
|
971
979
|
Returns:
|
972
980
|
Dict with generated file paths and checksums
|
973
981
|
"""
|
974
982
|
self.rich_console.print_header("Evidence Bundle Generation", "Enterprise Compliance Integration")
|
975
|
-
|
983
|
+
|
976
984
|
try:
|
977
985
|
# Use integrated VPC analyzer for evidence generation
|
978
986
|
evidence_files = self.vpc_analyzer.generate_cleanup_evidence(output_dir)
|
979
|
-
|
987
|
+
|
980
988
|
if evidence_files:
|
981
989
|
self.rich_console.print_success(
|
982
990
|
f"✅ Evidence bundle generated successfully!\n"
|
983
991
|
f"Files created: {len(evidence_files)}\n"
|
984
992
|
f"Output directory: {output_dir}"
|
985
993
|
)
|
986
|
-
|
994
|
+
|
987
995
|
# Display evidence summary for manager interface compatibility
|
988
996
|
self.rich_console.print_panel(
|
989
997
|
"Evidence Bundle Summary",
|
990
|
-
"\n".join(
|
991
|
-
|
992
|
-
|
998
|
+
"\n".join(
|
999
|
+
[
|
1000
|
+
f"• {evidence_type}: {file_path.split('/')[-1]}"
|
1001
|
+
for evidence_type, file_path in evidence_files.items()
|
1002
|
+
]
|
1003
|
+
),
|
1004
|
+
title="📋 AWSO-05 Evidence Files",
|
993
1005
|
)
|
994
1006
|
else:
|
995
1007
|
self.rich_console.print_warning("⚠️ No evidence files generated - run discovery and analysis first")
|
996
|
-
|
1008
|
+
|
997
1009
|
return evidence_files
|
998
|
-
|
1010
|
+
|
999
1011
|
except Exception as e:
|
1000
1012
|
logger.error(f"Evidence bundle generation failed: {e}")
|
1001
1013
|
self.rich_console.print_error(f"❌ Evidence bundle generation failed: {e}")
|
1002
1014
|
raise
|
1003
1015
|
|
1004
|
-
def execute_integrated_vpc_analysis(
|
1005
|
-
|
1016
|
+
def execute_integrated_vpc_analysis(
|
1017
|
+
self, vpc_ids: Optional[List[str]] = None, generate_evidence: bool = True
|
1018
|
+
) -> Dict[str, Any]:
|
1006
1019
|
"""
|
1007
1020
|
Execute complete integrated VPC analysis workflow using migrated VPC module capabilities.
|
1008
|
-
|
1021
|
+
|
1009
1022
|
Combines networking_wrapper.py, cost_engine.py, and manager_interface.py patterns
|
1010
1023
|
into a single comprehensive analysis workflow for enterprise VPC management.
|
1011
|
-
|
1024
|
+
|
1012
1025
|
Args:
|
1013
1026
|
vpc_ids: Optional list of specific VPC IDs to analyze
|
1014
1027
|
generate_evidence: Whether to generate evidence bundle
|
1015
|
-
|
1028
|
+
|
1016
1029
|
Returns:
|
1017
1030
|
Dict with complete analysis results and evidence files
|
1018
1031
|
"""
|
1019
1032
|
self.rich_console.print_header("Integrated VPC Analysis", "Complete VPC Module Integration")
|
1020
|
-
|
1033
|
+
|
1021
1034
|
workflow_results = {
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1035
|
+
"discovery_result": None,
|
1036
|
+
"awso_analysis": None,
|
1037
|
+
"evidence_files": None,
|
1038
|
+
"analysis_summary": {},
|
1026
1039
|
}
|
1027
|
-
|
1040
|
+
|
1028
1041
|
try:
|
1029
1042
|
# Step 1: VPC Topology Discovery
|
1030
1043
|
self.rich_console.print_info("🔍 Step 1: VPC Topology Discovery...")
|
1031
1044
|
discovery_result = self.discover_vpc_topology_comprehensive(vpc_ids)
|
1032
|
-
workflow_results[
|
1033
|
-
|
1045
|
+
workflow_results["discovery_result"] = discovery_result
|
1046
|
+
|
1034
1047
|
# Step 2: AWSO-05 Dependency Analysis
|
1035
1048
|
self.rich_console.print_info("🎯 Step 2: AWSO-05 Dependency Analysis...")
|
1036
1049
|
awso_analysis = self.analyze_awso_dependencies_comprehensive(discovery_result)
|
1037
|
-
workflow_results[
|
1038
|
-
|
1050
|
+
workflow_results["awso_analysis"] = awso_analysis
|
1051
|
+
|
1039
1052
|
# Step 3: Evidence Bundle Generation (if requested)
|
1040
1053
|
if generate_evidence:
|
1041
1054
|
self.rich_console.print_info("📋 Step 3: Evidence Bundle Generation...")
|
1042
1055
|
evidence_files = self.generate_vpc_evidence_bundle()
|
1043
|
-
workflow_results[
|
1044
|
-
|
1056
|
+
workflow_results["evidence_files"] = evidence_files
|
1057
|
+
|
1045
1058
|
# Step 4: Analysis Summary (manager interface compatibility)
|
1046
|
-
total_monthly_cost = sum([
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
]
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1059
|
+
total_monthly_cost = sum([nat["EstimatedMonthlyCost"] for nat in discovery_result.nat_gateways]) + sum(
|
1060
|
+
[ep["EstimatedMonthlyCost"] for ep in discovery_result.vpc_endpoints]
|
1061
|
+
)
|
1062
|
+
|
1063
|
+
workflow_results["analysis_summary"] = {
|
1064
|
+
"total_resources": discovery_result.total_resources,
|
1065
|
+
"estimated_monthly_cost": total_monthly_cost,
|
1066
|
+
"default_vpcs_found": len(awso_analysis.default_vpcs),
|
1067
|
+
"eni_gate_warnings": len(awso_analysis.eni_gate_warnings),
|
1068
|
+
"cleanup_recommendations": len(awso_analysis.cleanup_recommendations),
|
1069
|
+
"cleanup_readiness": awso_analysis.evidence_bundle.get("CleanupReadiness", "UNKNOWN"),
|
1070
|
+
"cis_benchmark_compliance": awso_analysis.evidence_bundle.get("ComplianceStatus", {}).get(
|
1071
|
+
"CISBenchmark", "UNKNOWN"
|
1072
|
+
),
|
1060
1073
|
}
|
1061
|
-
|
1074
|
+
|
1062
1075
|
# Display comprehensive summary
|
1063
|
-
summary = workflow_results[
|
1076
|
+
summary = workflow_results["analysis_summary"]
|
1064
1077
|
self.rich_console.print_panel(
|
1065
1078
|
"Integrated VPC Analysis Complete",
|
1066
1079
|
f"Total Resources Discovered: {summary['total_resources']}\n"
|
@@ -1070,11 +1083,11 @@ class VPCOperations(BaseOperation):
|
|
1070
1083
|
f"Cleanup Recommendations: {summary['cleanup_recommendations']}\n"
|
1071
1084
|
f"Cleanup Readiness: {summary['cleanup_readiness']}\n"
|
1072
1085
|
f"CIS Benchmark Status: {summary['cis_benchmark_compliance']}",
|
1073
|
-
title="🏆 VPC Module Integration Results"
|
1086
|
+
title="🏆 VPC Module Integration Results",
|
1074
1087
|
)
|
1075
|
-
|
1088
|
+
|
1076
1089
|
return workflow_results
|
1077
|
-
|
1090
|
+
|
1078
1091
|
except Exception as e:
|
1079
1092
|
logger.error(f"Integrated VPC analysis failed: {e}")
|
1080
1093
|
self.rich_console.print_error(f"❌ Integrated VPC analysis failed: {e}")
|
@@ -1392,52 +1405,54 @@ class VPCOperations(BaseOperation):
|
|
1392
1405
|
except Exception as e:
|
1393
1406
|
logger.warning(f"Could not get all regions, using defaults: {e}")
|
1394
1407
|
return ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
|
1395
|
-
|
1408
|
+
|
1396
1409
|
def _get_nat_gateway_monthly_cost(self) -> float:
|
1397
1410
|
"""
|
1398
1411
|
Get dynamic NAT Gateway monthly cost from AWS Pricing API.
|
1399
|
-
|
1412
|
+
|
1400
1413
|
Returns:
|
1401
1414
|
float: Monthly cost for NAT Gateway
|
1402
1415
|
"""
|
1403
1416
|
try:
|
1404
1417
|
# Use AWS Pricing API to get real NAT Gateway pricing
|
1405
|
-
pricing_client = self.session.client(
|
1406
|
-
|
1418
|
+
pricing_client = self.session.client("pricing", region_name="us-east-1")
|
1419
|
+
|
1407
1420
|
nat_gateway_response = pricing_client.get_products(
|
1408
|
-
ServiceCode=
|
1421
|
+
ServiceCode="AmazonVPC",
|
1409
1422
|
Filters=[
|
1410
|
-
{
|
1411
|
-
{
|
1423
|
+
{"Type": "TERM_MATCH", "Field": "productFamily", "Value": "NAT Gateway"},
|
1424
|
+
{"Type": "TERM_MATCH", "Field": "location", "Value": "US East (N. Virginia)"},
|
1412
1425
|
],
|
1413
|
-
MaxResults=1
|
1426
|
+
MaxResults=1,
|
1414
1427
|
)
|
1415
|
-
|
1416
|
-
if nat_gateway_response.get(
|
1428
|
+
|
1429
|
+
if nat_gateway_response.get("PriceList"):
|
1417
1430
|
import json
|
1418
|
-
|
1419
|
-
|
1431
|
+
|
1432
|
+
price_data = json.loads(nat_gateway_response["PriceList"][0])
|
1433
|
+
terms = price_data.get("terms", {}).get("OnDemand", {})
|
1420
1434
|
if terms:
|
1421
1435
|
term_data = list(terms.values())[0]
|
1422
|
-
price_dims = term_data.get(
|
1436
|
+
price_dims = term_data.get("priceDimensions", {})
|
1423
1437
|
if price_dims:
|
1424
1438
|
price_dim = list(price_dims.values())[0]
|
1425
|
-
usd_price = price_dim.get(
|
1426
|
-
if usd_price ==
|
1439
|
+
usd_price = price_dim.get("pricePerUnit", {}).get("USD", "0")
|
1440
|
+
if usd_price == "0" or not usd_price:
|
1427
1441
|
raise ValueError("No valid pricing found in AWS response")
|
1428
1442
|
hourly_rate = float(usd_price)
|
1429
1443
|
monthly_rate = hourly_rate * 24 * 30 # Convert to monthly
|
1430
1444
|
return monthly_rate
|
1431
|
-
|
1445
|
+
|
1432
1446
|
# Fallback to environment variable
|
1433
1447
|
import os
|
1434
|
-
|
1448
|
+
|
1449
|
+
env_nat_cost = os.getenv("NAT_GATEWAY_MONTHLY_COST")
|
1435
1450
|
if env_nat_cost:
|
1436
1451
|
return float(env_nat_cost)
|
1437
|
-
|
1452
|
+
|
1438
1453
|
# Final fallback: calculated estimate based on AWS pricing
|
1439
1454
|
return 32.4 # Current AWS NAT Gateway monthly rate (calculated, not hardcoded)
|
1440
|
-
|
1455
|
+
|
1441
1456
|
except Exception as e:
|
1442
1457
|
self.console.print(f"[yellow]Warning: Could not fetch NAT Gateway pricing: {e}[/yellow]")
|
1443
1458
|
return 32.4 # Calculated estimate
|
@@ -1450,8 +1465,10 @@ class VPCOperations(BaseOperation):
|
|
1450
1465
|
from enum import Enum
|
1451
1466
|
from pathlib import Path
|
1452
1467
|
|
1468
|
+
|
1453
1469
|
class BusinessPriority(Enum):
|
1454
1470
|
"""Business priority levels for manager decision making"""
|
1471
|
+
|
1455
1472
|
CRITICAL = "Critical"
|
1456
1473
|
HIGH = "High"
|
1457
1474
|
MEDIUM = "Medium"
|
@@ -1460,6 +1477,7 @@ class BusinessPriority(Enum):
|
|
1460
1477
|
|
1461
1478
|
class RiskLevel(Enum):
|
1462
1479
|
"""Risk assessment levels for business decisions"""
|
1480
|
+
|
1463
1481
|
MINIMAL = "Minimal"
|
1464
1482
|
LOW = "Low"
|
1465
1483
|
MEDIUM = "Medium"
|
@@ -1469,6 +1487,7 @@ class RiskLevel(Enum):
|
|
1469
1487
|
@dataclass
|
1470
1488
|
class BusinessRecommendation:
|
1471
1489
|
"""Business-focused recommendation structure migrated from vpc module"""
|
1490
|
+
|
1472
1491
|
title: str
|
1473
1492
|
executive_summary: str
|
1474
1493
|
monthly_savings: float
|
@@ -1486,6 +1505,7 @@ class BusinessRecommendation:
|
|
1486
1505
|
@dataclass
|
1487
1506
|
class ManagerDashboardConfig:
|
1488
1507
|
"""Configuration for manager dashboard behavior"""
|
1508
|
+
|
1489
1509
|
safety_mode: bool = True
|
1490
1510
|
auto_export: bool = True
|
1491
1511
|
executive_summaries_only: bool = False
|
@@ -1502,17 +1522,17 @@ class ManagerDashboardConfig:
|
|
1502
1522
|
class EnhancedVPCNetworkingManager(BaseOperation):
|
1503
1523
|
"""
|
1504
1524
|
Enhanced VPC Networking Manager - Migrated capabilities from vpc module
|
1505
|
-
|
1506
|
-
Integrates networking_wrapper.py, manager_interface.py, and cost_engine.py
|
1525
|
+
|
1526
|
+
Integrates networking_wrapper.py, manager_interface.py, and cost_engine.py
|
1507
1527
|
capabilities into operate module following "Do one thing and do it well" principle.
|
1508
|
-
|
1528
|
+
|
1509
1529
|
Provides enterprise VPC management with:
|
1510
1530
|
- Manager-friendly business interface
|
1511
1531
|
- Cost optimization with MCP validation
|
1512
1532
|
- Network topology management
|
1513
1533
|
- Safety-first operations with approval workflows
|
1514
1534
|
"""
|
1515
|
-
|
1535
|
+
|
1516
1536
|
def __init__(self, dry_run: bool = True):
|
1517
1537
|
super().__init__(dry_run=dry_run)
|
1518
1538
|
self.operation_name = "enhanced_vpc_networking"
|
@@ -1520,55 +1540,64 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1520
1540
|
self.analysis_results = {}
|
1521
1541
|
self.business_recommendations = []
|
1522
1542
|
self.export_directory = Path("./tmp/manager_dashboard")
|
1523
|
-
|
1543
|
+
|
1524
1544
|
# Enhanced cost model integration using new AWS pricing API with enterprise fallback
|
1525
1545
|
try:
|
1526
1546
|
from ..common.aws_pricing_api import pricing_api
|
1527
1547
|
import os
|
1528
|
-
|
1548
|
+
|
1529
1549
|
# Enable fallback mode for operational compatibility
|
1530
|
-
os.environ[
|
1531
|
-
|
1550
|
+
os.environ["AWS_PRICING_STRICT_COMPLIANCE"] = os.getenv("AWS_PRICING_STRICT_COMPLIANCE", "false")
|
1551
|
+
|
1532
1552
|
# Get dynamic pricing for all VPC services with enhanced fallback
|
1533
1553
|
nat_monthly = pricing_api.get_nat_gateway_monthly_cost(self.region)
|
1534
|
-
|
1554
|
+
|
1535
1555
|
# Convert to expected units
|
1536
1556
|
self.nat_gateway_hourly_cost = nat_monthly / (24 * 30) # Monthly to hourly
|
1537
1557
|
self.nat_gateway_data_processing = self.nat_gateway_hourly_cost # Same rate for data
|
1538
|
-
|
1558
|
+
|
1539
1559
|
# Use proportional pricing for other services
|
1540
1560
|
self.transit_gateway_monthly_cost = nat_monthly * 1.11 # TGW slightly higher than NAT
|
1541
1561
|
self.vpc_endpoint_hourly_cost = self.nat_gateway_hourly_cost * 0.22 # VPC Endpoint lower
|
1542
|
-
|
1543
|
-
logger.info(
|
1544
|
-
|
1545
|
-
|
1562
|
+
|
1563
|
+
logger.info(
|
1564
|
+
f"✅ Enhanced VPC pricing loaded: NAT=${self.nat_gateway_hourly_cost:.4f}/hr, "
|
1565
|
+
f"TGW=${self.transit_gateway_monthly_cost:.2f}/mo, VPCEndpoint=${self.vpc_endpoint_hourly_cost:.4f}/hr"
|
1566
|
+
)
|
1567
|
+
|
1546
1568
|
except Exception as e:
|
1547
1569
|
logger.warning(f"⚠️ Enhanced pricing API fallback: {e}")
|
1548
1570
|
# Use config-based pricing as final fallback
|
1549
1571
|
try:
|
1550
1572
|
from ..vpc.config import load_config
|
1573
|
+
|
1551
1574
|
vpc_config = load_config()
|
1552
|
-
|
1575
|
+
|
1553
1576
|
self.nat_gateway_hourly_cost = vpc_config.cost_model.nat_gateway_hourly
|
1554
1577
|
self.nat_gateway_data_processing = vpc_config.cost_model.nat_gateway_data_processing
|
1555
1578
|
self.transit_gateway_monthly_cost = vpc_config.cost_model.transit_gateway_monthly
|
1556
1579
|
self.vpc_endpoint_hourly_cost = vpc_config.cost_model.vpc_endpoint_interface_hourly
|
1557
|
-
|
1558
|
-
logger.info(
|
1559
|
-
|
1560
|
-
|
1580
|
+
|
1581
|
+
logger.info(
|
1582
|
+
f"✅ Config-based VPC pricing loaded: NAT=${self.nat_gateway_hourly_cost:.4f}/hr, "
|
1583
|
+
f"TGW=${self.transit_gateway_monthly_cost:.2f}/mo, VPCEndpoint=${self.vpc_endpoint_hourly_cost:.4f}/hr"
|
1584
|
+
)
|
1585
|
+
|
1561
1586
|
except Exception as config_error:
|
1562
1587
|
logger.error(f"🚫 All pricing methods failed: {config_error}")
|
1563
|
-
logger.error(
|
1564
|
-
|
1565
|
-
|
1588
|
+
logger.error(
|
1589
|
+
"💡 Ensure AWS credentials are configured or set AWS_PRICING_OVERRIDE_* environment variables"
|
1590
|
+
)
|
1591
|
+
raise RuntimeError(
|
1592
|
+
"Unable to get pricing for VPC analysis. Check AWS credentials and IAM permissions."
|
1593
|
+
) from config_error
|
1594
|
+
|
1566
1595
|
def execute_operation(self, context: OperationContext, operation_type: str, **kwargs) -> List[OperationResult]:
|
1567
1596
|
"""Enhanced VPC operations with manager interface support"""
|
1568
1597
|
if operation_type.startswith("analyze_network_topology"):
|
1569
1598
|
return self._analyze_network_topology_comprehensive(context, **kwargs)
|
1570
1599
|
elif operation_type.startswith("generate_manager_report"):
|
1571
|
-
return self._generate_manager_report(context, **kwargs)
|
1600
|
+
return self._generate_manager_report(context, **kwargs)
|
1572
1601
|
elif operation_type.startswith("optimize_vpc_costs"):
|
1573
1602
|
return self._optimize_vpc_costs_comprehensive(context, **kwargs)
|
1574
1603
|
elif operation_type.startswith("analyze_nat_gateway_usage"):
|
@@ -1578,16 +1607,16 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1578
1607
|
else:
|
1579
1608
|
# Fall back to base VPC operations
|
1580
1609
|
return super().execute_operation(context, operation_type, **kwargs)
|
1581
|
-
|
1610
|
+
|
1582
1611
|
def _analyze_network_topology_comprehensive(self, context: OperationContext, **kwargs) -> List[OperationResult]:
|
1583
1612
|
"""
|
1584
1613
|
Comprehensive network topology analysis migrated from networking_wrapper.py
|
1585
1614
|
"""
|
1586
1615
|
result = self.create_operation_result(context, "analyze_network_topology", "vpc", "network-topology")
|
1587
|
-
|
1616
|
+
|
1588
1617
|
try:
|
1589
|
-
self.rich_console.print_header("Comprehensive Network Topology Analysis", "
|
1590
|
-
|
1618
|
+
self.rich_console.print_header("Comprehensive Network Topology Analysis", "latest version")
|
1619
|
+
|
1591
1620
|
topology_analysis = {
|
1592
1621
|
"timestamp": datetime.now().isoformat(),
|
1593
1622
|
"account_id": context.account_id,
|
@@ -1595,64 +1624,66 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1595
1624
|
"vpc_topology": {},
|
1596
1625
|
"cost_analysis": {},
|
1597
1626
|
"optimization_opportunities": [],
|
1598
|
-
"business_recommendations": []
|
1627
|
+
"business_recommendations": [],
|
1599
1628
|
}
|
1600
|
-
|
1629
|
+
|
1601
1630
|
ec2_client = self.get_client("ec2", context.region)
|
1602
|
-
|
1631
|
+
|
1603
1632
|
# Get comprehensive VPC data
|
1604
1633
|
vpcs_response = self.execute_aws_call(ec2_client, "describe_vpcs")
|
1605
1634
|
nat_response = self.execute_aws_call(ec2_client, "describe_nat_gateways")
|
1606
1635
|
endpoints_response = self.execute_aws_call(ec2_client, "describe_vpc_endpoints")
|
1607
|
-
|
1636
|
+
|
1608
1637
|
# Process VPCs with enhanced metadata
|
1609
1638
|
vpcs_data = []
|
1610
1639
|
total_monthly_cost = 0
|
1611
|
-
|
1640
|
+
|
1612
1641
|
for vpc in vpcs_response["Vpcs"]:
|
1613
1642
|
vpc_analysis = self._analyze_vpc_comprehensive(ec2_client, vpc, context)
|
1614
1643
|
vpcs_data.append(vpc_analysis)
|
1615
1644
|
total_monthly_cost += vpc_analysis.get("estimated_monthly_cost", 0)
|
1616
|
-
|
1645
|
+
|
1617
1646
|
# Process NAT Gateways
|
1618
1647
|
nat_gateways_data = []
|
1619
1648
|
for nat in nat_response["NatGateways"]:
|
1620
1649
|
if nat["State"] != "deleted":
|
1621
1650
|
nat_analysis = self._analyze_nat_gateway_costs(ec2_client, nat, context)
|
1622
1651
|
nat_gateways_data.append(nat_analysis)
|
1623
|
-
|
1652
|
+
|
1624
1653
|
# Generate business recommendations
|
1625
1654
|
business_recommendations = self._generate_business_recommendations(
|
1626
1655
|
vpcs_data, nat_gateways_data, total_monthly_cost
|
1627
1656
|
)
|
1628
|
-
|
1629
|
-
topology_analysis.update(
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1657
|
+
|
1658
|
+
topology_analysis.update(
|
1659
|
+
{
|
1660
|
+
"vpcs": vpcs_data,
|
1661
|
+
"nat_gateways": nat_gateways_data,
|
1662
|
+
"vpc_endpoints": self._analyze_vpc_endpoints(endpoints_response["VpcEndpoints"]),
|
1663
|
+
"total_monthly_cost": total_monthly_cost,
|
1664
|
+
"total_annual_cost": total_monthly_cost * 12,
|
1665
|
+
"business_recommendations": business_recommendations,
|
1666
|
+
}
|
1667
|
+
)
|
1668
|
+
|
1638
1669
|
# Display results with Rich formatting
|
1639
1670
|
self._display_topology_analysis(topology_analysis)
|
1640
|
-
|
1671
|
+
|
1641
1672
|
result.mark_completed(OperationStatus.SUCCESS)
|
1642
1673
|
result.response_data = topology_analysis
|
1643
|
-
|
1674
|
+
|
1644
1675
|
except Exception as e:
|
1645
1676
|
error_msg = f"Network topology analysis failed: {e}"
|
1646
1677
|
result.mark_completed(OperationStatus.FAILED, error_msg)
|
1647
1678
|
self.rich_console.print_error(f"❌ {error_msg}")
|
1648
1679
|
logger.error(error_msg)
|
1649
|
-
|
1680
|
+
|
1650
1681
|
return [result]
|
1651
|
-
|
1682
|
+
|
1652
1683
|
def _analyze_vpc_comprehensive(self, ec2_client, vpc: Dict[str, Any], context: OperationContext) -> Dict[str, Any]:
|
1653
1684
|
"""Comprehensive VPC analysis with cost implications"""
|
1654
1685
|
tags = {tag["Key"]: tag["Value"] for tag in vpc.get("Tags", [])}
|
1655
|
-
|
1686
|
+
|
1656
1687
|
vpc_analysis = {
|
1657
1688
|
"vpc_id": vpc["VpcId"],
|
1658
1689
|
"cidr_block": vpc["CidrBlock"],
|
@@ -1664,48 +1695,51 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1664
1695
|
"security_groups": [],
|
1665
1696
|
"route_tables": [],
|
1666
1697
|
"estimated_monthly_cost": 0,
|
1667
|
-
"optimization_opportunities": []
|
1698
|
+
"optimization_opportunities": [],
|
1668
1699
|
}
|
1669
|
-
|
1700
|
+
|
1670
1701
|
try:
|
1671
1702
|
# Get subnets
|
1672
1703
|
subnets_response = self.execute_aws_call(
|
1673
|
-
ec2_client, "describe_subnets",
|
1674
|
-
Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]
|
1704
|
+
ec2_client, "describe_subnets", Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]
|
1675
1705
|
)
|
1676
|
-
|
1706
|
+
|
1677
1707
|
for subnet in subnets_response["Subnets"]:
|
1678
1708
|
subnet_tags = {tag["Key"]: tag["Value"] for tag in subnet.get("Tags", [])}
|
1679
|
-
vpc_analysis["subnets"].append(
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1709
|
+
vpc_analysis["subnets"].append(
|
1710
|
+
{
|
1711
|
+
"subnet_id": subnet["SubnetId"],
|
1712
|
+
"cidr_block": subnet["CidrBlock"],
|
1713
|
+
"availability_zone": subnet["AvailabilityZone"],
|
1714
|
+
"available_ip_address_count": subnet["AvailableIpAddressCount"],
|
1715
|
+
"map_public_ip_on_launch": subnet.get("MapPublicIpOnLaunch", False),
|
1716
|
+
"tags": subnet_tags,
|
1717
|
+
"name": subnet_tags.get("Name", subnet["SubnetId"]),
|
1718
|
+
}
|
1719
|
+
)
|
1720
|
+
|
1689
1721
|
# Basic cost estimation (placeholder for more sophisticated analysis)
|
1690
1722
|
if len(vpc_analysis["subnets"]) > 10:
|
1691
|
-
vpc_analysis["optimization_opportunities"].append(
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1723
|
+
vpc_analysis["optimization_opportunities"].append(
|
1724
|
+
{
|
1725
|
+
"type": "subnet_consolidation",
|
1726
|
+
"description": f"VPC has {len(vpc_analysis['subnets'])} subnets - consider consolidation",
|
1727
|
+
"potential_savings": "Reduced management overhead",
|
1728
|
+
}
|
1729
|
+
)
|
1730
|
+
|
1697
1731
|
except Exception as e:
|
1698
1732
|
logger.warning(f"Failed to get detailed VPC data for {vpc['VpcId']}: {e}")
|
1699
|
-
|
1733
|
+
|
1700
1734
|
return vpc_analysis
|
1701
|
-
|
1735
|
+
|
1702
1736
|
def _analyze_nat_gateway_costs(self, ec2_client, nat: Dict[str, Any], context: OperationContext) -> Dict[str, Any]:
|
1703
1737
|
"""Detailed NAT Gateway cost analysis"""
|
1704
1738
|
tags = {tag["Key"]: tag["Value"] for tag in nat.get("Tags", [])}
|
1705
|
-
|
1739
|
+
|
1706
1740
|
# Base monthly cost (24/7 * 30 days * $0.045/hour)
|
1707
1741
|
base_monthly_cost = 24 * 30 * self.nat_gateway_hourly_cost
|
1708
|
-
|
1742
|
+
|
1709
1743
|
nat_analysis = {
|
1710
1744
|
"nat_gateway_id": nat["NatGatewayId"],
|
1711
1745
|
"state": nat["State"],
|
@@ -1717,94 +1751,104 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1717
1751
|
"base_monthly_cost": base_monthly_cost,
|
1718
1752
|
"estimated_data_processing_cost": 0, # Would need CloudWatch metrics for accurate calculation
|
1719
1753
|
"total_estimated_monthly_cost": base_monthly_cost,
|
1720
|
-
"optimization_recommendation": "monitor_usage"
|
1754
|
+
"optimization_recommendation": "monitor_usage",
|
1721
1755
|
}
|
1722
|
-
|
1756
|
+
|
1723
1757
|
# Add optimization recommendations based on analysis
|
1724
1758
|
if nat["State"] == "available":
|
1725
1759
|
nat_analysis["optimization_recommendation"] = "monitor_usage"
|
1726
1760
|
elif nat["State"] in ["pending", "failed"]:
|
1727
1761
|
nat_analysis["optimization_recommendation"] = "investigate_health"
|
1728
|
-
|
1762
|
+
|
1729
1763
|
return nat_analysis
|
1730
|
-
|
1764
|
+
|
1731
1765
|
def _analyze_vpc_endpoints(self, endpoints: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
1732
1766
|
"""Analyze VPC endpoints with cost implications"""
|
1733
1767
|
endpoints_analysis = []
|
1734
|
-
|
1768
|
+
|
1735
1769
|
for endpoint in endpoints:
|
1736
1770
|
tags = {tag["Key"]: tag["Value"] for tag in endpoint.get("Tags", [])}
|
1737
|
-
|
1771
|
+
|
1738
1772
|
# Estimate monthly cost based on endpoint type
|
1739
1773
|
if endpoint.get("VpcEndpointType") == "Interface":
|
1740
|
-
estimated_monthly_cost =
|
1774
|
+
estimated_monthly_cost = (
|
1775
|
+
24 * 30 * self.vpc_endpoint_hourly_cost
|
1776
|
+
) # Interface endpoints have hourly charges
|
1741
1777
|
else:
|
1742
1778
|
estimated_monthly_cost = 0 # Gateway endpoints are typically free
|
1743
|
-
|
1744
|
-
endpoints_analysis.append(
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1779
|
+
|
1780
|
+
endpoints_analysis.append(
|
1781
|
+
{
|
1782
|
+
"vpc_endpoint_id": endpoint["VpcEndpointId"],
|
1783
|
+
"vpc_id": endpoint.get("VpcId"),
|
1784
|
+
"service_name": endpoint.get("ServiceName"),
|
1785
|
+
"endpoint_type": endpoint.get("VpcEndpointType"),
|
1786
|
+
"state": endpoint.get("State"),
|
1787
|
+
"tags": tags,
|
1788
|
+
"name": tags.get("Name", endpoint["VpcEndpointId"]),
|
1789
|
+
"estimated_monthly_cost": estimated_monthly_cost,
|
1790
|
+
"route_table_ids": endpoint.get("RouteTableIds", []),
|
1791
|
+
"subnet_ids": endpoint.get("SubnetIds", []),
|
1792
|
+
}
|
1793
|
+
)
|
1794
|
+
|
1757
1795
|
return endpoints_analysis
|
1758
|
-
|
1759
|
-
def _generate_business_recommendations(
|
1796
|
+
|
1797
|
+
def _generate_business_recommendations(
|
1798
|
+
self, vpcs_data: List[Dict], nat_data: List[Dict], total_cost: float
|
1799
|
+
) -> List[BusinessRecommendation]:
|
1760
1800
|
"""Generate business-focused recommendations"""
|
1761
1801
|
recommendations = []
|
1762
|
-
|
1802
|
+
|
1763
1803
|
# NAT Gateway optimization recommendation
|
1764
1804
|
active_nat_gateways = [nat for nat in nat_data if nat["state"] == "available"]
|
1765
1805
|
if len(active_nat_gateways) > len(vpcs_data):
|
1766
1806
|
# Dynamic NAT Gateway cost from AWS Pricing API - NO hardcoded values
|
1767
1807
|
nat_gateway_monthly_cost = self._get_nat_gateway_monthly_cost()
|
1768
1808
|
potential_savings = (len(active_nat_gateways) - len(vpcs_data)) * nat_gateway_monthly_cost
|
1769
|
-
|
1770
|
-
recommendations.append(
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1809
|
+
|
1810
|
+
recommendations.append(
|
1811
|
+
BusinessRecommendation(
|
1812
|
+
title="NAT Gateway Consolidation Opportunity",
|
1813
|
+
executive_summary=f"Multiple NAT Gateways detected ({len(active_nat_gateways)}) across {len(vpcs_data)} VPCs",
|
1814
|
+
monthly_savings=potential_savings,
|
1815
|
+
annual_impact=potential_savings * 12,
|
1816
|
+
implementation_timeline="2-4 weeks",
|
1817
|
+
business_priority=BusinessPriority.HIGH if potential_savings > 90 else BusinessPriority.MEDIUM,
|
1818
|
+
risk_level=RiskLevel.LOW,
|
1819
|
+
resource_requirements=["Network engineer", "1-2 hours/NAT Gateway"],
|
1820
|
+
success_metrics=[f"Reduce monthly NAT Gateway costs by ${potential_savings:.2f}"],
|
1821
|
+
approval_required=potential_savings > self.manager_config.approval_threshold,
|
1822
|
+
quick_win=potential_savings > 50,
|
1823
|
+
strategic_value="Cost optimization without service impact",
|
1824
|
+
)
|
1825
|
+
)
|
1826
|
+
|
1827
|
+
# VPC complexity recommendation
|
1786
1828
|
complex_vpcs = [vpc for vpc in vpcs_data if len(vpc.get("subnets", [])) > 15]
|
1787
1829
|
if complex_vpcs:
|
1788
|
-
recommendations.append(
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1830
|
+
recommendations.append(
|
1831
|
+
BusinessRecommendation(
|
1832
|
+
title="VPC Architecture Simplification",
|
1833
|
+
executive_summary=f"Detected {len(complex_vpcs)} VPCs with high subnet complexity",
|
1834
|
+
monthly_savings=0, # Operational savings, not direct cost
|
1835
|
+
annual_impact=0,
|
1836
|
+
implementation_timeline="6-12 weeks",
|
1837
|
+
business_priority=BusinessPriority.MEDIUM,
|
1838
|
+
risk_level=RiskLevel.MEDIUM,
|
1839
|
+
resource_requirements=["Cloud architect", "Network engineer", "4-8 hours/VPC"],
|
1840
|
+
success_metrics=["Reduced operational complexity", "Improved maintainability"],
|
1841
|
+
approval_required=True,
|
1842
|
+
quick_win=False,
|
1843
|
+
strategic_value="Improved operational efficiency and reduced management overhead",
|
1844
|
+
)
|
1845
|
+
)
|
1846
|
+
|
1803
1847
|
return recommendations
|
1804
|
-
|
1848
|
+
|
1805
1849
|
def _display_topology_analysis(self, topology_analysis: Dict[str, Any]) -> None:
|
1806
1850
|
"""Display topology analysis with Rich formatting"""
|
1807
|
-
|
1851
|
+
|
1808
1852
|
# Summary panel
|
1809
1853
|
summary_text = (
|
1810
1854
|
f"Total VPCs: {len(topology_analysis.get('vpcs', []))}\n"
|
@@ -1813,44 +1857,49 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1813
1857
|
f"Estimated monthly cost: ${topology_analysis.get('total_monthly_cost', 0):.2f}\n"
|
1814
1858
|
f"Estimated annual cost: ${topology_analysis.get('total_annual_cost', 0):.2f}"
|
1815
1859
|
)
|
1816
|
-
|
1817
|
-
self.rich_console.print_panel(
|
1818
|
-
|
1819
|
-
summary_text,
|
1820
|
-
title="🏗️ Infrastructure Overview"
|
1821
|
-
)
|
1822
|
-
|
1860
|
+
|
1861
|
+
self.rich_console.print_panel("Network Topology Summary", summary_text, title="🏗️ Infrastructure Overview")
|
1862
|
+
|
1823
1863
|
# Business recommendations table
|
1824
|
-
recommendations = topology_analysis.get(
|
1864
|
+
recommendations = topology_analysis.get("business_recommendations", [])
|
1825
1865
|
if recommendations:
|
1826
1866
|
rec_data = []
|
1827
1867
|
for rec in recommendations:
|
1828
|
-
rec_data.append(
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1868
|
+
rec_data.append(
|
1869
|
+
[
|
1870
|
+
rec.title,
|
1871
|
+
rec.business_priority.value,
|
1872
|
+
f"${rec.monthly_savings:.2f}",
|
1873
|
+
f"${rec.annual_impact:.2f}",
|
1874
|
+
rec.implementation_timeline,
|
1875
|
+
"Yes" if rec.approval_required else "No",
|
1876
|
+
]
|
1877
|
+
)
|
1878
|
+
|
1837
1879
|
self.rich_console.print_table(
|
1838
1880
|
rec_data,
|
1839
|
-
headers=[
|
1840
|
-
|
1881
|
+
headers=[
|
1882
|
+
"Recommendation",
|
1883
|
+
"Priority",
|
1884
|
+
"Monthly Savings",
|
1885
|
+
"Annual Impact",
|
1886
|
+
"Timeline",
|
1887
|
+
"Approval Required",
|
1888
|
+
],
|
1889
|
+
title="💡 Business Optimization Recommendations",
|
1841
1890
|
)
|
1842
|
-
|
1891
|
+
|
1843
1892
|
def _generate_manager_report(self, context: OperationContext, **kwargs) -> List[OperationResult]:
|
1844
1893
|
"""Generate manager-friendly business report"""
|
1845
1894
|
result = self.create_operation_result(context, "generate_manager_report", "vpc", "manager-report")
|
1846
|
-
|
1895
|
+
|
1847
1896
|
try:
|
1848
|
-
self.rich_console.print_header("Manager Dashboard - VPC Cost Optimization", "
|
1849
|
-
|
1897
|
+
self.rich_console.print_header("Manager Dashboard - VPC Cost Optimization", "latest version")
|
1898
|
+
|
1850
1899
|
# First run comprehensive analysis
|
1851
1900
|
analysis_results = self._analyze_network_topology_comprehensive(context, **kwargs)
|
1852
1901
|
analysis_data = analysis_results[0].response_data
|
1853
|
-
|
1902
|
+
|
1854
1903
|
# Generate executive summary
|
1855
1904
|
executive_report = {
|
1856
1905
|
"report_generated": datetime.now().isoformat(),
|
@@ -1858,86 +1907,89 @@ class EnhancedVPCNetworkingManager(BaseOperation):
|
|
1858
1907
|
"total_infrastructure_cost": analysis_data.get("total_monthly_cost", 0),
|
1859
1908
|
"optimization_opportunities": len(analysis_data.get("business_recommendations", [])),
|
1860
1909
|
"immediate_actions": [
|
1861
|
-
rec
|
1910
|
+
rec
|
1911
|
+
for rec in analysis_data.get("business_recommendations", [])
|
1862
1912
|
if rec.quick_win and rec.business_priority in [BusinessPriority.HIGH, BusinessPriority.CRITICAL]
|
1863
1913
|
],
|
1864
1914
|
"approval_required_items": [
|
1865
|
-
rec for rec in analysis_data.get("business_recommendations", [])
|
1866
|
-
|
1867
|
-
]
|
1915
|
+
rec for rec in analysis_data.get("business_recommendations", []) if rec.approval_required
|
1916
|
+
],
|
1868
1917
|
},
|
1869
1918
|
"detailed_analysis": analysis_data,
|
1870
|
-
"next_steps": self._generate_next_steps(analysis_data.get("business_recommendations", []))
|
1919
|
+
"next_steps": self._generate_next_steps(analysis_data.get("business_recommendations", [])),
|
1871
1920
|
}
|
1872
|
-
|
1921
|
+
|
1873
1922
|
# Display executive summary
|
1874
1923
|
self._display_executive_summary(executive_report["executive_summary"])
|
1875
|
-
|
1924
|
+
|
1876
1925
|
result.mark_completed(OperationStatus.SUCCESS)
|
1877
1926
|
result.response_data = executive_report
|
1878
|
-
|
1927
|
+
|
1879
1928
|
except Exception as e:
|
1880
1929
|
error_msg = f"Manager report generation failed: {e}"
|
1881
1930
|
result.mark_completed(OperationStatus.FAILED, error_msg)
|
1882
1931
|
self.rich_console.print_error(f"❌ {error_msg}")
|
1883
1932
|
logger.error(error_msg)
|
1884
|
-
|
1933
|
+
|
1885
1934
|
return [result]
|
1886
|
-
|
1935
|
+
|
1887
1936
|
def _generate_next_steps(self, recommendations: List[BusinessRecommendation]) -> List[Dict[str, str]]:
|
1888
1937
|
"""Generate actionable next steps for managers"""
|
1889
1938
|
next_steps = []
|
1890
|
-
|
1939
|
+
|
1891
1940
|
high_priority = [rec for rec in recommendations if rec.business_priority == BusinessPriority.HIGH]
|
1892
1941
|
quick_wins = [rec for rec in recommendations if rec.quick_win]
|
1893
|
-
|
1942
|
+
|
1894
1943
|
if high_priority:
|
1895
|
-
next_steps.append(
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1944
|
+
next_steps.append(
|
1945
|
+
{
|
1946
|
+
"action": "Review High Priority Items",
|
1947
|
+
"description": f"Review {len(high_priority)} high-priority optimization opportunities",
|
1948
|
+
"timeline": "This week",
|
1949
|
+
}
|
1950
|
+
)
|
1951
|
+
|
1901
1952
|
if quick_wins:
|
1902
|
-
next_steps.append(
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1953
|
+
next_steps.append(
|
1954
|
+
{
|
1955
|
+
"action": "Implement Quick Wins",
|
1956
|
+
"description": f"Execute {len(quick_wins)} quick-win optimizations",
|
1957
|
+
"timeline": "Next 2 weeks",
|
1958
|
+
}
|
1959
|
+
)
|
1960
|
+
|
1961
|
+
next_steps.append(
|
1962
|
+
{
|
1963
|
+
"action": "Schedule Technical Review",
|
1964
|
+
"description": "Meet with technical team to discuss implementation details",
|
1965
|
+
"timeline": "Within 2 weeks",
|
1966
|
+
}
|
1967
|
+
)
|
1968
|
+
|
1914
1969
|
return next_steps
|
1915
|
-
|
1970
|
+
|
1916
1971
|
def _display_executive_summary(self, summary: Dict[str, Any]) -> None:
|
1917
1972
|
"""Display executive summary with business-friendly formatting"""
|
1918
|
-
|
1973
|
+
|
1919
1974
|
summary_text = (
|
1920
1975
|
f"Monthly Infrastructure Cost: ${summary.get('total_infrastructure_cost', 0):.2f}\n"
|
1921
1976
|
f"Optimization Opportunities: {summary.get('optimization_opportunities', 0)}\n"
|
1922
1977
|
f"Quick Win Actions: {len(summary.get('immediate_actions', []))}\n"
|
1923
1978
|
f"Items Requiring Approval: {len(summary.get('approval_required_items', []))}"
|
1924
1979
|
)
|
1925
|
-
|
1926
|
-
self.rich_console.print_panel(
|
1927
|
-
"Executive Dashboard",
|
1928
|
-
summary_text,
|
1929
|
-
title="📊 Business Overview"
|
1930
|
-
)
|
1980
|
+
|
1981
|
+
self.rich_console.print_panel("Executive Dashboard", summary_text, title="📊 Business Overview")
|
1931
1982
|
|
1932
1983
|
|
1933
1984
|
# =============================================================================
|
1934
1985
|
# AWSO-5 VPC Cleanup Operations - Enterprise Security Framework
|
1935
1986
|
# =============================================================================
|
1936
1987
|
|
1988
|
+
|
1937
1989
|
@dataclass
|
1938
1990
|
class VPCCleanupConfiguration:
|
1939
1991
|
"""AWSO-5 VPC cleanup configuration with enterprise safety controls."""
|
1940
|
-
|
1992
|
+
|
1941
1993
|
vpc_id: str
|
1942
1994
|
dry_run: bool = True
|
1943
1995
|
force_cleanup: bool = False
|
@@ -1945,12 +1997,12 @@ class VPCCleanupConfiguration:
|
|
1945
1997
|
evidence_collection: bool = True
|
1946
1998
|
platform_lead_approval: bool = False
|
1947
1999
|
skip_eni_gate: bool = False # DANGEROUS: Only for emergency scenarios
|
1948
|
-
|
2000
|
+
|
1949
2001
|
def __post_init__(self):
|
1950
2002
|
"""Validation of cleanup configuration."""
|
1951
2003
|
if self.force_cleanup and not self.approval_token:
|
1952
2004
|
raise ValueError("Force cleanup requires approval token")
|
1953
|
-
|
2005
|
+
|
1954
2006
|
if self.skip_eni_gate and not self.platform_lead_approval:
|
1955
2007
|
raise ValueError("Skipping ENI gate requires Platform Lead approval")
|
1956
2008
|
|
@@ -1958,42 +2010,42 @@ class VPCCleanupConfiguration:
|
|
1958
2010
|
@dataclass
|
1959
2011
|
class VPCCleanupPlan:
|
1960
2012
|
"""AWSO-5 VPC cleanup execution plan with ordered steps."""
|
1961
|
-
|
2013
|
+
|
1962
2014
|
vpc_id: str
|
1963
2015
|
cleanup_steps: List[Dict[str, Any]]
|
1964
2016
|
estimated_duration_minutes: int
|
1965
2017
|
risk_level: str # LOW, MEDIUM, HIGH
|
1966
2018
|
requires_approval: bool
|
1967
|
-
|
2019
|
+
|
1968
2020
|
# Evidence collection
|
1969
2021
|
pre_cleanup_evidence: Dict[str, Any]
|
1970
2022
|
plan_hash: str
|
1971
2023
|
plan_timestamp: str
|
1972
|
-
|
2024
|
+
|
1973
2025
|
@property
|
1974
2026
|
def total_steps(self) -> int:
|
1975
2027
|
"""Total number of cleanup steps."""
|
1976
2028
|
return len(self.cleanup_steps)
|
1977
|
-
|
2029
|
+
|
1978
2030
|
@property
|
1979
2031
|
def blocking_steps(self) -> int:
|
1980
2032
|
"""Number of steps that could cause service disruption."""
|
1981
|
-
return len([step for step in self.cleanup_steps if step.get(
|
2033
|
+
return len([step for step in self.cleanup_steps if step.get("risk_level") == "HIGH"])
|
1982
2034
|
|
1983
2035
|
|
1984
2036
|
class AWSO5VPCCleanupOperation(BaseOperation):
|
1985
2037
|
"""
|
1986
2038
|
AWSO-5 VPC Cleanup Operation - Enterprise Security Framework.
|
1987
|
-
|
2039
|
+
|
1988
2040
|
Implements comprehensive VPC cleanup following the AWSO-5 12-step framework
|
1989
2041
|
with enterprise safety controls, evidence collection, and approval workflows.
|
1990
|
-
|
2042
|
+
|
1991
2043
|
**Strategic Alignment**:
|
1992
2044
|
- Security posture enhancement through default VPC elimination
|
1993
|
-
- Attack surface reduction via systematic dependency cleanup
|
2045
|
+
- Attack surface reduction via systematic dependency cleanup
|
1994
2046
|
- CIS Benchmark compliance through infrastructure hygiene
|
1995
2047
|
- Evidence-based validation with SHA256-verified audit trails
|
1996
|
-
|
2048
|
+
|
1997
2049
|
**Safety Controls**:
|
1998
2050
|
- ENI gate validation (prevents accidental workload disruption)
|
1999
2051
|
- Dry-run first approach with detailed execution plans
|
@@ -2001,52 +2053,48 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2001
2053
|
- Comprehensive rollback procedures
|
2002
2054
|
- Real-time monitoring and validation
|
2003
2055
|
"""
|
2004
|
-
|
2056
|
+
|
2005
2057
|
def __init__(self, context: OperationContext):
|
2006
2058
|
"""Initialize AWSO-5 VPC cleanup operation."""
|
2007
2059
|
super().__init__(context)
|
2008
2060
|
self.operation_type = "AWSO5_VPC_CLEANUP"
|
2009
|
-
|
2061
|
+
|
2010
2062
|
# Initialize dependency analyzer
|
2011
2063
|
from runbooks.inventory.vpc_dependency_analyzer import VPCDependencyAnalyzer
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
)
|
2016
|
-
|
2064
|
+
|
2065
|
+
self.dependency_analyzer = VPCDependencyAnalyzer(session=self.context.session, region=self.context.region)
|
2066
|
+
|
2017
2067
|
# Cleanup tracking
|
2018
2068
|
self.cleanup_evidence: Dict[str, Any] = {}
|
2019
2069
|
self.cleanup_plan: Optional[VPCCleanupPlan] = None
|
2020
|
-
|
2070
|
+
|
2021
2071
|
def execute_vpc_cleanup(
|
2022
|
-
self,
|
2023
|
-
config: VPCCleanupConfiguration,
|
2024
|
-
evidence_bundle_path: Optional[str] = None
|
2072
|
+
self, config: VPCCleanupConfiguration, evidence_bundle_path: Optional[str] = None
|
2025
2073
|
) -> List[OperationResult]:
|
2026
2074
|
"""
|
2027
2075
|
Execute AWSO-5 VPC cleanup with comprehensive safety validation.
|
2028
|
-
|
2076
|
+
|
2029
2077
|
Args:
|
2030
2078
|
config: VPC cleanup configuration
|
2031
2079
|
evidence_bundle_path: Optional path to save evidence bundle
|
2032
|
-
|
2080
|
+
|
2033
2081
|
Returns:
|
2034
2082
|
Operation results with evidence and audit information
|
2035
2083
|
"""
|
2036
2084
|
results = []
|
2037
2085
|
operation_start = datetime.utcnow()
|
2038
|
-
|
2086
|
+
|
2039
2087
|
try:
|
2040
2088
|
# Phase 1: Pre-cleanup Analysis & Validation
|
2041
2089
|
self.rich_console.print_header("AWSO-5 VPC Cleanup Operation", "1.0.0")
|
2042
2090
|
self.rich_console.print_info(f"Target VPC: {config.vpc_id}")
|
2043
2091
|
self.rich_console.print_info(f"Mode: {'DRY-RUN' if config.dry_run else 'EXECUTE'}")
|
2044
|
-
|
2092
|
+
|
2045
2093
|
# Step 1: ENI Gate Validation (Critical Safety Check)
|
2046
2094
|
if not config.skip_eni_gate:
|
2047
2095
|
self.rich_console.print_info("Step 1: ENI Gate Validation (Critical Safety Check)")
|
2048
2096
|
dependency_result = self.dependency_analyzer.analyze_vpc_dependencies(config.vpc_id)
|
2049
|
-
|
2097
|
+
|
2050
2098
|
if dependency_result.eni_count > 0:
|
2051
2099
|
error_msg = f"ENI Gate FAILED: {dependency_result.eni_count} active ENIs detected"
|
2052
2100
|
self.rich_console.print_error(f"❌ {error_msg}")
|
@@ -2055,33 +2103,33 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2055
2103
|
self.rich_console.print_info("1. Investigate ENI owners and workload requirements")
|
2056
2104
|
self.rich_console.print_info("2. Coordinate with application teams")
|
2057
2105
|
self.rich_console.print_info("3. Consider migration vs cleanup options")
|
2058
|
-
|
2106
|
+
|
2059
2107
|
result = OperationResult(
|
2060
2108
|
operation_id=self.context.operation_id,
|
2061
2109
|
operation_type="AWSO5_ENI_GATE_CHECK",
|
2062
2110
|
status=OperationStatus.FAILED,
|
2063
2111
|
message=error_msg,
|
2064
2112
|
details={
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
}
|
2113
|
+
"eni_count": dependency_result.eni_count,
|
2114
|
+
"vpc_id": config.vpc_id,
|
2115
|
+
"recommendation": "INVESTIGATE_REQUIRED",
|
2116
|
+
"dependencies": [dep.__dict__ for dep in dependency_result.dependencies],
|
2117
|
+
},
|
2070
2118
|
)
|
2071
2119
|
return [result]
|
2072
|
-
|
2120
|
+
|
2073
2121
|
self.rich_console.print_success("✅ ENI Gate PASSED - No active ENIs detected")
|
2074
2122
|
else:
|
2075
2123
|
self.rich_console.print_warning("⚠️ ENI Gate SKIPPED (Platform Lead Approval Required)")
|
2076
2124
|
dependency_result = self.dependency_analyzer.analyze_vpc_dependencies(config.vpc_id)
|
2077
|
-
|
2125
|
+
|
2078
2126
|
# Step 2: Generate Cleanup Plan
|
2079
2127
|
self.rich_console.print_info("Step 2: Generating Comprehensive Cleanup Plan")
|
2080
2128
|
self.cleanup_plan = self._generate_cleanup_plan(dependency_result, config)
|
2081
|
-
|
2129
|
+
|
2082
2130
|
# Display cleanup plan
|
2083
2131
|
self._display_cleanup_plan(self.cleanup_plan)
|
2084
|
-
|
2132
|
+
|
2085
2133
|
# Step 3: Risk Assessment & Approval Gate
|
2086
2134
|
if self.cleanup_plan.requires_approval and not config.approval_token:
|
2087
2135
|
self.rich_console.print_warning("⚠️ This cleanup requires Platform Lead approval")
|
@@ -2090,21 +2138,21 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2090
2138
|
self.rich_console.print_info("• Platform Lead (Default VPC deletion)")
|
2091
2139
|
if self.cleanup_plan.risk_level == "HIGH":
|
2092
2140
|
self.rich_console.print_info("• Additional stakeholder review")
|
2093
|
-
|
2141
|
+
|
2094
2142
|
result = OperationResult(
|
2095
2143
|
operation_id=self.context.operation_id,
|
2096
2144
|
operation_type="AWSO5_APPROVAL_REQUIRED",
|
2097
2145
|
status=OperationStatus.PENDING_APPROVAL,
|
2098
2146
|
message="Platform Lead approval required for VPC cleanup",
|
2099
2147
|
details={
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
}
|
2148
|
+
"vpc_id": config.vpc_id,
|
2149
|
+
"risk_level": self.cleanup_plan.risk_level,
|
2150
|
+
"requires_approval_reason": "Default VPC or High Risk Operation",
|
2151
|
+
"cleanup_plan": self.cleanup_plan.__dict__,
|
2152
|
+
},
|
2105
2153
|
)
|
2106
2154
|
return [result]
|
2107
|
-
|
2155
|
+
|
2108
2156
|
# Step 4: Execute Cleanup (Dry-run or Actual)
|
2109
2157
|
if config.dry_run:
|
2110
2158
|
self.rich_console.print_info("Step 3: DRY-RUN Mode - No actual changes will be made")
|
@@ -2112,86 +2160,84 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2112
2160
|
else:
|
2113
2161
|
self.rich_console.print_info("Step 3: EXECUTING VPC Cleanup")
|
2114
2162
|
result = self._execute_actual_cleanup(self.cleanup_plan, config)
|
2115
|
-
|
2163
|
+
|
2116
2164
|
# Step 5: Evidence Bundle Generation
|
2117
2165
|
if config.evidence_collection:
|
2118
2166
|
self.rich_console.print_info("Step 4: Generating Evidence Bundle")
|
2119
2167
|
evidence_bundle = self._generate_evidence_bundle(
|
2120
|
-
dependency_result,
|
2121
|
-
self.cleanup_plan,
|
2122
|
-
result,
|
2123
|
-
evidence_bundle_path
|
2168
|
+
dependency_result, self.cleanup_plan, result, evidence_bundle_path
|
2124
2169
|
)
|
2125
|
-
result.details[
|
2126
|
-
|
2170
|
+
result.details["evidence_bundle"] = evidence_bundle
|
2171
|
+
|
2127
2172
|
results.append(result)
|
2128
|
-
|
2173
|
+
|
2129
2174
|
except Exception as e:
|
2130
2175
|
error_msg = f"AWSO-5 VPC cleanup failed: {str(e)}"
|
2131
2176
|
logger.exception(error_msg)
|
2132
|
-
|
2177
|
+
|
2133
2178
|
result = OperationResult(
|
2134
2179
|
operation_id=self.context.operation_id,
|
2135
2180
|
operation_type="AWSO5_VPC_CLEANUP_ERROR",
|
2136
2181
|
status=OperationStatus.FAILED,
|
2137
2182
|
message=error_msg,
|
2138
2183
|
details={
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
}
|
2184
|
+
"vpc_id": config.vpc_id,
|
2185
|
+
"error_type": type(e).__name__,
|
2186
|
+
"operation_duration": (datetime.utcnow() - operation_start).total_seconds(),
|
2187
|
+
},
|
2143
2188
|
)
|
2144
2189
|
results.append(result)
|
2145
|
-
|
2190
|
+
|
2146
2191
|
return results
|
2147
|
-
|
2148
|
-
def _generate_cleanup_plan(
|
2149
|
-
self,
|
2150
|
-
dependency_result,
|
2151
|
-
config: VPCCleanupConfiguration
|
2152
|
-
) -> VPCCleanupPlan:
|
2192
|
+
|
2193
|
+
def _generate_cleanup_plan(self, dependency_result, config: VPCCleanupConfiguration) -> VPCCleanupPlan:
|
2153
2194
|
"""Generate comprehensive VPC cleanup plan based on dependency analysis."""
|
2154
|
-
|
2195
|
+
|
2155
2196
|
cleanup_steps = []
|
2156
2197
|
risk_level = "LOW"
|
2157
2198
|
estimated_duration = 5 # Base time in minutes
|
2158
|
-
|
2199
|
+
|
2159
2200
|
# Generate cleanup steps based on dependencies
|
2160
2201
|
for dependency in dependency_result.dependencies:
|
2161
2202
|
if dependency.is_blocking:
|
2162
2203
|
step = {
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2204
|
+
"step_type": "DEPENDENCY_CLEANUP",
|
2205
|
+
"resource_type": dependency.resource_type,
|
2206
|
+
"resource_id": dependency.resource_id,
|
2207
|
+
"action": dependency.remediation_action,
|
2208
|
+
"risk_level": "HIGH"
|
2209
|
+
if dependency.resource_type in ["LoadBalancer", "TransitGatewayAttachment"]
|
2210
|
+
else "MEDIUM",
|
2211
|
+
"estimated_minutes": self._estimate_cleanup_time(dependency.resource_type),
|
2169
2212
|
}
|
2170
2213
|
cleanup_steps.append(step)
|
2171
|
-
estimated_duration += step[
|
2172
|
-
|
2173
|
-
if step[
|
2214
|
+
estimated_duration += step["estimated_minutes"]
|
2215
|
+
|
2216
|
+
if step["risk_level"] == "HIGH":
|
2174
2217
|
risk_level = "HIGH"
|
2175
|
-
elif step[
|
2218
|
+
elif step["risk_level"] == "MEDIUM" and risk_level != "HIGH":
|
2176
2219
|
risk_level = "MEDIUM"
|
2177
|
-
|
2220
|
+
|
2178
2221
|
# Final VPC deletion step
|
2179
|
-
cleanup_steps.append(
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2222
|
+
cleanup_steps.append(
|
2223
|
+
{
|
2224
|
+
"step_type": "VPC_DELETION",
|
2225
|
+
"resource_type": "VPC",
|
2226
|
+
"resource_id": config.vpc_id,
|
2227
|
+
"action": "Delete VPC (final step)",
|
2228
|
+
"risk_level": "MEDIUM",
|
2229
|
+
"estimated_minutes": 2,
|
2230
|
+
}
|
2231
|
+
)
|
2187
2232
|
estimated_duration += 2
|
2188
|
-
|
2233
|
+
|
2189
2234
|
# Calculate plan hash for integrity
|
2190
2235
|
import hashlib
|
2191
2236
|
import json
|
2237
|
+
|
2192
2238
|
plan_content = json.dumps(cleanup_steps, sort_keys=True)
|
2193
2239
|
plan_hash = hashlib.sha256(plan_content.encode()).hexdigest()
|
2194
|
-
|
2240
|
+
|
2195
2241
|
return VPCCleanupPlan(
|
2196
2242
|
vpc_id=config.vpc_id,
|
2197
2243
|
cleanup_steps=cleanup_steps,
|
@@ -2200,72 +2246,68 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2200
2246
|
requires_approval=dependency_result.is_default or risk_level == "HIGH",
|
2201
2247
|
pre_cleanup_evidence=dependency_result.__dict__,
|
2202
2248
|
plan_hash=plan_hash[:16], # Short hash for display
|
2203
|
-
plan_timestamp=datetime.utcnow().isoformat()
|
2249
|
+
plan_timestamp=datetime.utcnow().isoformat(),
|
2204
2250
|
)
|
2205
|
-
|
2251
|
+
|
2206
2252
|
def _estimate_cleanup_time(self, resource_type: str) -> int:
|
2207
2253
|
"""Estimate cleanup time in minutes for different resource types."""
|
2208
2254
|
time_estimates = {
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2255
|
+
"NetworkInterface": 3,
|
2256
|
+
"NatGateway": 5,
|
2257
|
+
"InternetGateway": 2,
|
2258
|
+
"RouteTable": 2,
|
2259
|
+
"VpcEndpoint": 3,
|
2260
|
+
"TransitGatewayAttachment": 10,
|
2261
|
+
"VpcPeeringConnection": 3,
|
2262
|
+
"ResolverEndpoint": 5,
|
2263
|
+
"LoadBalancer": 8,
|
2264
|
+
"SecurityGroup": 2,
|
2265
|
+
"NetworkAcl": 2,
|
2266
|
+
"FlowLog": 1,
|
2221
2267
|
}
|
2222
2268
|
return time_estimates.get(resource_type, 3)
|
2223
|
-
|
2269
|
+
|
2224
2270
|
def _display_cleanup_plan(self, plan: VPCCleanupPlan) -> None:
|
2225
2271
|
"""Display comprehensive cleanup plan with Rich formatting."""
|
2226
|
-
|
2272
|
+
|
2227
2273
|
# Plan Summary
|
2228
|
-
summary_table = create_table(
|
2229
|
-
title="AWSO-5 VPC Cleanup Plan Summary"
|
2230
|
-
)
|
2274
|
+
summary_table = create_table(title="AWSO-5 VPC Cleanup Plan Summary")
|
2231
2275
|
summary_table.add_column("Metric", style="cyan")
|
2232
2276
|
summary_table.add_column("Value", style="green")
|
2233
|
-
|
2277
|
+
|
2234
2278
|
summary_table.add_row("VPC ID", plan.vpc_id)
|
2235
2279
|
summary_table.add_row("Total Steps", str(plan.total_steps))
|
2236
2280
|
summary_table.add_row("Estimated Duration", f"{plan.estimated_duration_minutes} minutes")
|
2237
2281
|
summary_table.add_row("Risk Level", plan.risk_level)
|
2238
2282
|
summary_table.add_row("Requires Approval", "Yes" if plan.requires_approval else "No")
|
2239
2283
|
summary_table.add_row("Plan Hash", plan.plan_hash)
|
2240
|
-
|
2284
|
+
|
2241
2285
|
self.rich_console.print("\n")
|
2242
2286
|
self.rich_console.print(summary_table)
|
2243
|
-
|
2287
|
+
|
2244
2288
|
# Cleanup Steps Detail
|
2245
2289
|
if plan.cleanup_steps:
|
2246
|
-
steps_table = create_table(
|
2247
|
-
title="Cleanup Execution Steps"
|
2248
|
-
)
|
2290
|
+
steps_table = create_table(title="Cleanup Execution Steps")
|
2249
2291
|
steps_table.add_column("Step", style="cyan")
|
2250
|
-
steps_table.add_column("Resource Type", style="blue")
|
2292
|
+
steps_table.add_column("Resource Type", style="blue")
|
2251
2293
|
steps_table.add_column("Resource ID", style="green")
|
2252
2294
|
steps_table.add_column("Action", style="yellow")
|
2253
2295
|
steps_table.add_column("Risk", style="red")
|
2254
2296
|
steps_table.add_column("Est. Time", style="magenta")
|
2255
|
-
|
2297
|
+
|
2256
2298
|
for i, step in enumerate(plan.cleanup_steps, 1):
|
2257
2299
|
steps_table.add_row(
|
2258
2300
|
str(i),
|
2259
|
-
step[
|
2260
|
-
step[
|
2261
|
-
step[
|
2262
|
-
step[
|
2263
|
-
f"{step['estimated_minutes']}min"
|
2301
|
+
step["resource_type"],
|
2302
|
+
step["resource_id"],
|
2303
|
+
step["action"],
|
2304
|
+
step["risk_level"],
|
2305
|
+
f"{step['estimated_minutes']}min",
|
2264
2306
|
)
|
2265
|
-
|
2307
|
+
|
2266
2308
|
self.rich_console.print("\n")
|
2267
2309
|
self.rich_console.print(steps_table)
|
2268
|
-
|
2310
|
+
|
2269
2311
|
# Risk Assessment
|
2270
2312
|
if plan.risk_level == "HIGH":
|
2271
2313
|
self.rich_console.print_warning("⚠️ HIGH RISK: This cleanup involves critical infrastructure components")
|
@@ -2273,309 +2315,300 @@ class AWSO5VPCCleanupOperation(BaseOperation):
|
|
2273
2315
|
self.rich_console.print_info("ℹ️ MEDIUM RISK: Standard cleanup with network dependencies")
|
2274
2316
|
else:
|
2275
2317
|
self.rich_console.print_success("✅ LOW RISK: Straightforward cleanup with minimal dependencies")
|
2276
|
-
|
2277
|
-
def _execute_dry_run_cleanup(
|
2278
|
-
self,
|
2279
|
-
plan: VPCCleanupPlan,
|
2280
|
-
config: VPCCleanupConfiguration
|
2281
|
-
) -> OperationResult:
|
2318
|
+
|
2319
|
+
def _execute_dry_run_cleanup(self, plan: VPCCleanupPlan, config: VPCCleanupConfiguration) -> OperationResult:
|
2282
2320
|
"""Execute dry-run cleanup showing what would be done."""
|
2283
|
-
|
2321
|
+
|
2284
2322
|
self.rich_console.print_success("🔍 DRY-RUN: Simulating cleanup execution")
|
2285
|
-
|
2323
|
+
|
2286
2324
|
simulated_results = []
|
2287
2325
|
for i, step in enumerate(plan.cleanup_steps, 1):
|
2288
2326
|
self.rich_console.print_info(f"Step {i}/{plan.total_steps}: Would {step['action']}")
|
2289
|
-
simulated_results.append(
|
2290
|
-
|
2291
|
-
|
2292
|
-
|
2293
|
-
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2327
|
+
simulated_results.append(
|
2328
|
+
{
|
2329
|
+
"step": i,
|
2330
|
+
"resource_type": step["resource_type"],
|
2331
|
+
"resource_id": step["resource_id"],
|
2332
|
+
"action": step["action"],
|
2333
|
+
"simulated_result": "SUCCESS",
|
2334
|
+
"notes": "Dry-run simulation - no actual changes made",
|
2335
|
+
}
|
2336
|
+
)
|
2297
2337
|
time.sleep(0.5) # Simulate processing time
|
2298
|
-
|
2338
|
+
|
2299
2339
|
self.rich_console.print_success("✅ DRY-RUN completed successfully")
|
2300
2340
|
self.rich_console.print_info("Next Steps:")
|
2301
2341
|
self.rich_console.print_info("1. Review cleanup plan and risk assessment")
|
2302
2342
|
self.rich_console.print_info("2. Obtain required approvals if needed")
|
2303
2343
|
self.rich_console.print_info("3. Execute with --dry-run=false when ready")
|
2304
|
-
|
2344
|
+
|
2305
2345
|
return OperationResult(
|
2306
2346
|
operation_id=self.context.operation_id,
|
2307
2347
|
operation_type="AWSO5_DRY_RUN_CLEANUP",
|
2308
2348
|
status=OperationStatus.COMPLETED,
|
2309
2349
|
message="Dry-run cleanup completed successfully",
|
2310
2350
|
details={
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
}
|
2351
|
+
"vpc_id": config.vpc_id,
|
2352
|
+
"cleanup_plan": plan.__dict__,
|
2353
|
+
"simulated_results": simulated_results,
|
2354
|
+
"execution_mode": "DRY_RUN",
|
2355
|
+
},
|
2316
2356
|
)
|
2317
|
-
|
2318
|
-
def _execute_actual_cleanup(
|
2319
|
-
self,
|
2320
|
-
plan: VPCCleanupPlan,
|
2321
|
-
config: VPCCleanupConfiguration
|
2322
|
-
) -> OperationResult:
|
2357
|
+
|
2358
|
+
def _execute_actual_cleanup(self, plan: VPCCleanupPlan, config: VPCCleanupConfiguration) -> OperationResult:
|
2323
2359
|
"""Execute actual VPC cleanup with comprehensive error handling."""
|
2324
|
-
|
2360
|
+
|
2325
2361
|
self.rich_console.print_warning("⚠️ EXECUTING ACTUAL CLEANUP - This will make real changes!")
|
2326
|
-
|
2362
|
+
|
2327
2363
|
execution_results = []
|
2328
2364
|
failed_steps = []
|
2329
|
-
|
2365
|
+
|
2330
2366
|
try:
|
2331
2367
|
for i, step in enumerate(plan.cleanup_steps, 1):
|
2332
2368
|
self.rich_console.print_info(f"Step {i}/{plan.total_steps}: {step['action']}")
|
2333
|
-
|
2369
|
+
|
2334
2370
|
try:
|
2335
2371
|
# Execute cleanup step
|
2336
2372
|
step_result = self._execute_cleanup_step(step, config)
|
2337
2373
|
execution_results.append(step_result)
|
2338
|
-
|
2339
|
-
if step_result[
|
2374
|
+
|
2375
|
+
if step_result["success"]:
|
2340
2376
|
self.rich_console.print_success(f"✅ Step {i} completed: {step['resource_id']}")
|
2341
2377
|
else:
|
2342
2378
|
self.rich_console.print_error(f"❌ Step {i} failed: {step_result['error']}")
|
2343
|
-
failed_steps.append((i, step, step_result[
|
2344
|
-
|
2379
|
+
failed_steps.append((i, step, step_result["error"]))
|
2380
|
+
|
2345
2381
|
# Decide whether to continue or abort
|
2346
|
-
if step[
|
2382
|
+
if step["risk_level"] == "HIGH":
|
2347
2383
|
self.rich_console.print_error("🛑 ABORTING: High-risk step failed")
|
2348
2384
|
break
|
2349
|
-
|
2385
|
+
|
2350
2386
|
except Exception as e:
|
2351
2387
|
error_msg = f"Step {i} execution error: {str(e)}"
|
2352
2388
|
self.rich_console.print_error(f"❌ {error_msg}")
|
2353
2389
|
failed_steps.append((i, step, error_msg))
|
2354
|
-
|
2390
|
+
|
2355
2391
|
# Critical failure handling
|
2356
|
-
if step[
|
2392
|
+
if step["risk_level"] == "HIGH":
|
2357
2393
|
self.rich_console.print_error("🛑 CRITICAL FAILURE: Aborting cleanup")
|
2358
2394
|
break
|
2359
|
-
|
2395
|
+
|
2360
2396
|
# Final status assessment
|
2361
2397
|
if not failed_steps:
|
2362
2398
|
status = OperationStatus.COMPLETED
|
2363
2399
|
message = "AWSO-5 VPC cleanup completed successfully"
|
2364
2400
|
self.rich_console.print_success("✅ All cleanup steps completed successfully")
|
2365
2401
|
elif len(failed_steps) < len(plan.cleanup_steps) // 2:
|
2366
|
-
status = OperationStatus.PARTIALLY_COMPLETED
|
2402
|
+
status = OperationStatus.PARTIALLY_COMPLETED
|
2367
2403
|
message = f"VPC cleanup partially completed ({len(failed_steps)} steps failed)"
|
2368
2404
|
self.rich_console.print_warning(f"⚠️ Partial completion: {len(failed_steps)} steps failed")
|
2369
2405
|
else:
|
2370
2406
|
status = OperationStatus.FAILED
|
2371
2407
|
message = f"VPC cleanup failed ({len(failed_steps)} steps failed)"
|
2372
2408
|
self.rich_console.print_error(f"❌ Cleanup failed: {len(failed_steps)} steps failed")
|
2373
|
-
|
2409
|
+
|
2374
2410
|
# Post-cleanup validation
|
2375
2411
|
self.rich_console.print_info("Performing post-cleanup validation...")
|
2376
2412
|
post_validation = self._perform_post_cleanup_validation(config.vpc_id)
|
2377
|
-
|
2413
|
+
|
2378
2414
|
return OperationResult(
|
2379
2415
|
operation_id=self.context.operation_id,
|
2380
2416
|
operation_type="AWSO5_ACTUAL_CLEANUP",
|
2381
2417
|
status=status,
|
2382
2418
|
message=message,
|
2383
2419
|
details={
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
2387
|
-
|
2388
|
-
|
2389
|
-
|
2390
|
-
}
|
2420
|
+
"vpc_id": config.vpc_id,
|
2421
|
+
"cleanup_plan": plan.__dict__,
|
2422
|
+
"execution_results": execution_results,
|
2423
|
+
"failed_steps": failed_steps,
|
2424
|
+
"post_validation": post_validation,
|
2425
|
+
"execution_mode": "ACTUAL",
|
2426
|
+
},
|
2391
2427
|
)
|
2392
|
-
|
2428
|
+
|
2393
2429
|
except Exception as e:
|
2394
2430
|
error_msg = f"Critical cleanup error: {str(e)}"
|
2395
2431
|
logger.exception(error_msg)
|
2396
|
-
|
2432
|
+
|
2397
2433
|
return OperationResult(
|
2398
2434
|
operation_id=self.context.operation_id,
|
2399
2435
|
operation_type="AWSO5_CLEANUP_ERROR",
|
2400
2436
|
status=OperationStatus.FAILED,
|
2401
2437
|
message=error_msg,
|
2402
|
-
details={
|
2403
|
-
'vpc_id': config.vpc_id,
|
2404
|
-
'error_type': type(e).__name__,
|
2405
|
-
'partial_results': execution_results
|
2406
|
-
}
|
2438
|
+
details={"vpc_id": config.vpc_id, "error_type": type(e).__name__, "partial_results": execution_results},
|
2407
2439
|
)
|
2408
|
-
|
2440
|
+
|
2409
2441
|
def _execute_cleanup_step(self, step: Dict[str, Any], config: VPCCleanupConfiguration) -> Dict[str, Any]:
|
2410
2442
|
"""Execute a single cleanup step with AWS API calls."""
|
2411
|
-
|
2412
|
-
resource_type = step[
|
2413
|
-
resource_id = step[
|
2414
|
-
|
2443
|
+
|
2444
|
+
resource_type = step["resource_type"]
|
2445
|
+
resource_id = step["resource_id"]
|
2446
|
+
|
2415
2447
|
try:
|
2416
|
-
if resource_type ==
|
2417
|
-
self.context.session.client(
|
2418
|
-
|
2419
|
-
elif resource_type ==
|
2420
|
-
ec2 = self.context.session.client(
|
2448
|
+
if resource_type == "NatGateway":
|
2449
|
+
self.context.session.client("ec2").delete_nat_gateway(NatGatewayId=resource_id)
|
2450
|
+
|
2451
|
+
elif resource_type == "InternetGateway":
|
2452
|
+
ec2 = self.context.session.client("ec2")
|
2421
2453
|
# First detach, then delete
|
2422
2454
|
ec2.detach_internet_gateway(InternetGatewayId=resource_id, VpcId=config.vpc_id)
|
2423
2455
|
ec2.delete_internet_gateway(InternetGatewayId=resource_id)
|
2424
|
-
|
2425
|
-
elif resource_type ==
|
2426
|
-
ec2 = self.context.session.client(
|
2456
|
+
|
2457
|
+
elif resource_type == "RouteTable":
|
2458
|
+
ec2 = self.context.session.client("ec2")
|
2427
2459
|
# Disassociate first, then delete
|
2428
|
-
route_tables = ec2.describe_route_tables(RouteTableIds=[resource_id])[
|
2460
|
+
route_tables = ec2.describe_route_tables(RouteTableIds=[resource_id])["RouteTables"]
|
2429
2461
|
for rt in route_tables:
|
2430
|
-
for assoc in rt.get(
|
2431
|
-
if not assoc.get(
|
2432
|
-
ec2.disassociate_route_table(AssociationId=assoc[
|
2462
|
+
for assoc in rt.get("Associations", []):
|
2463
|
+
if not assoc.get("Main"):
|
2464
|
+
ec2.disassociate_route_table(AssociationId=assoc["RouteTableAssociationId"])
|
2433
2465
|
ec2.delete_route_table(RouteTableId=resource_id)
|
2434
|
-
|
2435
|
-
elif resource_type ==
|
2436
|
-
self.context.session.client(
|
2437
|
-
|
2438
|
-
elif resource_type ==
|
2439
|
-
self.context.session.client(
|
2466
|
+
|
2467
|
+
elif resource_type == "VpcEndpoint":
|
2468
|
+
self.context.session.client("ec2").delete_vpc_endpoints(VpcEndpointIds=[resource_id])
|
2469
|
+
|
2470
|
+
elif resource_type == "TransitGatewayAttachment":
|
2471
|
+
self.context.session.client("ec2").delete_transit_gateway_vpc_attachment(
|
2440
2472
|
TransitGatewayAttachmentId=resource_id
|
2441
2473
|
)
|
2442
|
-
|
2443
|
-
elif resource_type ==
|
2444
|
-
self.context.session.client(
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
|
2451
|
-
|
2452
|
-
|
2453
|
-
|
2454
|
-
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
|
2459
|
-
|
2460
|
-
elif resource_type == 'VPC':
|
2474
|
+
|
2475
|
+
elif resource_type == "VpcPeeringConnection":
|
2476
|
+
self.context.session.client("ec2").delete_vpc_peering_connection(VpcPeeringConnectionId=resource_id)
|
2477
|
+
|
2478
|
+
elif resource_type == "LoadBalancer":
|
2479
|
+
self.context.session.client("elbv2").delete_load_balancer(LoadBalancerArn=resource_id)
|
2480
|
+
|
2481
|
+
elif resource_type == "SecurityGroup":
|
2482
|
+
self.context.session.client("ec2").delete_security_group(GroupId=resource_id)
|
2483
|
+
|
2484
|
+
elif resource_type == "NetworkAcl":
|
2485
|
+
self.context.session.client("ec2").delete_network_acl(NetworkAclId=resource_id)
|
2486
|
+
|
2487
|
+
elif resource_type == "FlowLog":
|
2488
|
+
self.context.session.client("ec2").delete_flow_logs(FlowLogIds=[resource_id])
|
2489
|
+
|
2490
|
+
elif resource_type == "VPC":
|
2461
2491
|
# Final VPC deletion
|
2462
|
-
self.context.session.client(
|
2463
|
-
|
2492
|
+
self.context.session.client("ec2").delete_vpc(VpcId=resource_id)
|
2493
|
+
|
2464
2494
|
else:
|
2465
2495
|
return {
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2469
|
-
|
2496
|
+
"success": False,
|
2497
|
+
"resource_type": resource_type,
|
2498
|
+
"resource_id": resource_id,
|
2499
|
+
"error": f"Unknown resource type: {resource_type}",
|
2470
2500
|
}
|
2471
|
-
|
2501
|
+
|
2472
2502
|
return {
|
2473
|
-
|
2474
|
-
|
2475
|
-
|
2476
|
-
|
2503
|
+
"success": True,
|
2504
|
+
"resource_type": resource_type,
|
2505
|
+
"resource_id": resource_id,
|
2506
|
+
"timestamp": datetime.utcnow().isoformat(),
|
2477
2507
|
}
|
2478
|
-
|
2508
|
+
|
2479
2509
|
except ClientError as e:
|
2480
2510
|
return {
|
2481
|
-
|
2482
|
-
|
2483
|
-
|
2484
|
-
|
2511
|
+
"success": False,
|
2512
|
+
"resource_type": resource_type,
|
2513
|
+
"resource_id": resource_id,
|
2514
|
+
"error": f"AWS API Error: {e.response['Error']['Code']} - {e.response['Error']['Message']}",
|
2485
2515
|
}
|
2486
|
-
|
2516
|
+
|
2487
2517
|
def _perform_post_cleanup_validation(self, vpc_id: str) -> Dict[str, Any]:
|
2488
2518
|
"""Perform post-cleanup validation to ensure VPC and dependencies are removed."""
|
2489
|
-
|
2519
|
+
|
2490
2520
|
validation_results = {
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2521
|
+
"vpc_exists": False,
|
2522
|
+
"dependencies_remaining": [],
|
2523
|
+
"validation_timestamp": datetime.utcnow().isoformat(),
|
2524
|
+
"validation_passed": False,
|
2495
2525
|
}
|
2496
|
-
|
2526
|
+
|
2497
2527
|
try:
|
2498
2528
|
# Check if VPC still exists
|
2499
|
-
ec2 = self.context.session.client(
|
2529
|
+
ec2 = self.context.session.client("ec2")
|
2500
2530
|
try:
|
2501
2531
|
response = ec2.describe_vpcs(VpcIds=[vpc_id])
|
2502
|
-
if response[
|
2503
|
-
validation_results[
|
2532
|
+
if response["Vpcs"]:
|
2533
|
+
validation_results["vpc_exists"] = True
|
2504
2534
|
self.rich_console.print_warning(f"⚠️ VPC {vpc_id} still exists")
|
2505
2535
|
except ClientError as e:
|
2506
|
-
if
|
2507
|
-
validation_results[
|
2536
|
+
if "InvalidVpcID.NotFound" in str(e):
|
2537
|
+
validation_results["vpc_exists"] = False
|
2508
2538
|
self.rich_console.print_success(f"✅ VPC {vpc_id} successfully deleted")
|
2509
2539
|
else:
|
2510
2540
|
raise
|
2511
|
-
|
2541
|
+
|
2512
2542
|
# Check for remaining dependencies
|
2513
|
-
if not validation_results[
|
2514
|
-
validation_results[
|
2543
|
+
if not validation_results["vpc_exists"]:
|
2544
|
+
validation_results["validation_passed"] = True
|
2515
2545
|
self.rich_console.print_success("✅ Post-cleanup validation PASSED")
|
2516
2546
|
else:
|
2517
2547
|
# Re-run dependency analysis
|
2518
2548
|
dependency_result = self.dependency_analyzer.analyze_vpc_dependencies(vpc_id)
|
2519
|
-
validation_results[
|
2520
|
-
|
2521
|
-
|
2522
|
-
validation_results[
|
2523
|
-
|
2524
|
-
if validation_results['validation_passed']:
|
2549
|
+
validation_results["dependencies_remaining"] = [dep.__dict__ for dep in dependency_result.dependencies]
|
2550
|
+
validation_results["validation_passed"] = len(dependency_result.dependencies) == 0
|
2551
|
+
|
2552
|
+
if validation_results["validation_passed"]:
|
2525
2553
|
self.rich_console.print_success("✅ Post-cleanup validation PASSED - No dependencies remain")
|
2526
2554
|
else:
|
2527
|
-
self.rich_console.print_warning(
|
2528
|
-
|
2555
|
+
self.rich_console.print_warning(
|
2556
|
+
f"⚠️ {len(dependency_result.dependencies)} dependencies still exist"
|
2557
|
+
)
|
2558
|
+
|
2529
2559
|
except Exception as e:
|
2530
|
-
validation_results[
|
2531
|
-
validation_results[
|
2560
|
+
validation_results["error"] = str(e)
|
2561
|
+
validation_results["validation_passed"] = False
|
2532
2562
|
self.rich_console.print_error(f"❌ Post-cleanup validation failed: {str(e)}")
|
2533
|
-
|
2563
|
+
|
2534
2564
|
return validation_results
|
2535
|
-
|
2565
|
+
|
2536
2566
|
def _generate_evidence_bundle(
|
2537
2567
|
self,
|
2538
2568
|
dependency_result,
|
2539
2569
|
cleanup_plan: VPCCleanupPlan,
|
2540
2570
|
execution_result: OperationResult,
|
2541
|
-
evidence_bundle_path: Optional[str] = None
|
2571
|
+
evidence_bundle_path: Optional[str] = None,
|
2542
2572
|
) -> Dict[str, Any]:
|
2543
2573
|
"""Generate comprehensive evidence bundle for AWSO-5 compliance."""
|
2544
|
-
|
2574
|
+
|
2545
2575
|
evidence_bundle = {
|
2546
|
-
|
2547
|
-
|
2548
|
-
|
2549
|
-
|
2550
|
-
|
2551
|
-
|
2552
|
-
|
2576
|
+
"metadata": {
|
2577
|
+
"framework": "AWSO-5",
|
2578
|
+
"version": "1.0.0",
|
2579
|
+
"vpc_id": cleanup_plan.vpc_id,
|
2580
|
+
"timestamp": datetime.utcnow().isoformat(),
|
2581
|
+
"analyst": "AWSO5VPCCleanupOperation",
|
2582
|
+
"operation_id": self.context.operation_id,
|
2583
|
+
},
|
2584
|
+
"pre_cleanup_analysis": dependency_result.__dict__,
|
2585
|
+
"cleanup_plan": cleanup_plan.__dict__,
|
2586
|
+
"execution_results": execution_result.details,
|
2587
|
+
"evidence_artifacts": [],
|
2588
|
+
"compliance_validation": {
|
2589
|
+
"cis_benchmark_improvement": dependency_result.is_default,
|
2590
|
+
"attack_surface_reduction": True if execution_result.status == OperationStatus.COMPLETED else False,
|
2591
|
+
"security_posture_enhancement": len(dependency_result.dependencies) == 0,
|
2553
2592
|
},
|
2554
|
-
'pre_cleanup_analysis': dependency_result.__dict__,
|
2555
|
-
'cleanup_plan': cleanup_plan.__dict__,
|
2556
|
-
'execution_results': execution_result.details,
|
2557
|
-
'evidence_artifacts': [],
|
2558
|
-
'compliance_validation': {
|
2559
|
-
'cis_benchmark_improvement': dependency_result.is_default,
|
2560
|
-
'attack_surface_reduction': True if execution_result.status == OperationStatus.COMPLETED else False,
|
2561
|
-
'security_posture_enhancement': len(dependency_result.dependencies) == 0
|
2562
|
-
}
|
2563
2593
|
}
|
2564
|
-
|
2594
|
+
|
2565
2595
|
# Calculate evidence bundle hash
|
2566
2596
|
import hashlib
|
2567
2597
|
import json
|
2598
|
+
|
2568
2599
|
bundle_content = json.dumps(evidence_bundle, sort_keys=True, default=str)
|
2569
|
-
evidence_bundle[
|
2570
|
-
|
2600
|
+
evidence_bundle["bundle_hash"] = hashlib.sha256(bundle_content.encode()).hexdigest()
|
2601
|
+
|
2571
2602
|
# Save evidence bundle if path provided
|
2572
2603
|
if evidence_bundle_path:
|
2573
|
-
with open(evidence_bundle_path,
|
2604
|
+
with open(evidence_bundle_path, "w") as f:
|
2574
2605
|
json.dump(evidence_bundle, f, indent=2, default=str)
|
2575
2606
|
self.rich_console.print_success(f"Evidence bundle saved: {evidence_bundle_path}")
|
2576
|
-
|
2577
|
-
self.rich_console.print_success(
|
2578
|
-
|
2607
|
+
|
2608
|
+
self.rich_console.print_success(
|
2609
|
+
f"Evidence bundle generated with hash: {evidence_bundle['bundle_hash'][:16]}..."
|
2610
|
+
)
|
2611
|
+
|
2579
2612
|
return evidence_bundle
|
2580
2613
|
|
2581
2614
|
|
@@ -2585,11 +2618,11 @@ def execute_vpc_cleanup(
|
|
2585
2618
|
region: str = "us-east-1",
|
2586
2619
|
dry_run: bool = True,
|
2587
2620
|
approval_token: Optional[str] = None,
|
2588
|
-
evidence_bundle_path: Optional[str] = None
|
2621
|
+
evidence_bundle_path: Optional[str] = None,
|
2589
2622
|
) -> List[OperationResult]:
|
2590
2623
|
"""
|
2591
2624
|
CLI wrapper for AWSO-5 VPC cleanup operation.
|
2592
|
-
|
2625
|
+
|
2593
2626
|
Args:
|
2594
2627
|
vpc_id: AWS VPC identifier to cleanup
|
2595
2628
|
profile: AWS profile name
|
@@ -2597,28 +2630,25 @@ def execute_vpc_cleanup(
|
|
2597
2630
|
dry_run: Execute in dry-run mode (default: True)
|
2598
2631
|
approval_token: Platform lead approval token
|
2599
2632
|
evidence_bundle_path: Path to save evidence bundle
|
2600
|
-
|
2633
|
+
|
2601
2634
|
Returns:
|
2602
2635
|
Operation results with comprehensive cleanup information
|
2603
2636
|
"""
|
2604
2637
|
session = boto3.Session(profile_name=profile) if profile else boto3.Session()
|
2605
|
-
|
2638
|
+
|
2606
2639
|
# Create operation context
|
2607
2640
|
context = OperationContext(
|
2608
2641
|
operation_id=f"awso5-cleanup-{vpc_id}-{int(datetime.utcnow().timestamp())}",
|
2609
2642
|
region=region,
|
2610
2643
|
session=session,
|
2611
|
-
dry_run=dry_run
|
2644
|
+
dry_run=dry_run,
|
2612
2645
|
)
|
2613
|
-
|
2646
|
+
|
2614
2647
|
# Create cleanup configuration
|
2615
2648
|
config = VPCCleanupConfiguration(
|
2616
|
-
vpc_id=vpc_id,
|
2617
|
-
dry_run=dry_run,
|
2618
|
-
approval_token=approval_token,
|
2619
|
-
evidence_collection=True
|
2649
|
+
vpc_id=vpc_id, dry_run=dry_run, approval_token=approval_token, evidence_collection=True
|
2620
2650
|
)
|
2621
|
-
|
2651
|
+
|
2622
2652
|
# Execute cleanup operation
|
2623
2653
|
cleanup_operation = AWSO5VPCCleanupOperation(context)
|
2624
2654
|
return cleanup_operation.execute_vpc_cleanup(config, evidence_bundle_path)
|