runbooks 1.1.4__py3-none-any.whl → 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +135 -91
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +17 -12
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +99 -79
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +315 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +461 -454
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- runbooks-1.1.4.dist-info/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
runbooks/vpc/cleanup_wrapper.py
CHANGED
@@ -16,14 +16,7 @@ from rich.panel import Panel
|
|
16
16
|
from rich.prompt import Confirm, Prompt
|
17
17
|
|
18
18
|
from runbooks.common.profile_utils import get_profile_for_operation
|
19
|
-
from runbooks.common.rich_utils import
|
20
|
-
console,
|
21
|
-
print_header,
|
22
|
-
print_success,
|
23
|
-
print_error,
|
24
|
-
print_warning,
|
25
|
-
create_table
|
26
|
-
)
|
19
|
+
from runbooks.common.rich_utils import console, print_header, print_success, print_error, print_warning, create_table
|
27
20
|
from runbooks.common.mcp_integration import EnterpriseMCPIntegrator
|
28
21
|
from .vpc_cleanup_integration import VPCCleanupFramework, VPCCleanupPhase, VPCCleanupRisk
|
29
22
|
from .manager_interface import VPCManagerInterface
|
@@ -34,7 +27,7 @@ logger = logging.getLogger(__name__)
|
|
34
27
|
class VPCCleanupCLI:
|
35
28
|
"""
|
36
29
|
Enterprise VPC Cleanup CLI wrapper with safety controls and approval gates
|
37
|
-
|
30
|
+
|
38
31
|
Provides comprehensive VPC cleanup capabilities integrated with the existing
|
39
32
|
runbooks framework architecture and enterprise multi-account patterns.
|
40
33
|
"""
|
@@ -44,11 +37,11 @@ class VPCCleanupCLI:
|
|
44
37
|
profile: Optional[str] = None,
|
45
38
|
region: str = "us-east-1",
|
46
39
|
safety_mode: bool = True,
|
47
|
-
console: Optional[Console] = None
|
40
|
+
console: Optional[Console] = None,
|
48
41
|
):
|
49
42
|
"""
|
50
43
|
Initialize VPC Cleanup CLI
|
51
|
-
|
44
|
+
|
52
45
|
Args:
|
53
46
|
profile: AWS profile for operations
|
54
47
|
region: AWS region
|
@@ -59,45 +52,39 @@ class VPCCleanupCLI:
|
|
59
52
|
self.region = region
|
60
53
|
self.safety_mode = safety_mode
|
61
54
|
self.console = console or Console()
|
62
|
-
|
55
|
+
|
63
56
|
# Initialize cleanup framework
|
64
57
|
self.cleanup_framework = VPCCleanupFramework(
|
65
|
-
profile=profile,
|
66
|
-
region=region,
|
67
|
-
console=self.console,
|
68
|
-
safety_mode=safety_mode
|
58
|
+
profile=profile, region=region, console=self.console, safety_mode=safety_mode
|
69
59
|
)
|
70
|
-
|
60
|
+
|
71
61
|
# Initialize manager interface for business reporting
|
72
62
|
self.manager_interface = VPCManagerInterface(console=self.console)
|
73
|
-
|
63
|
+
|
74
64
|
# Initialize MCP integrator for cross-validation
|
75
|
-
self.mcp_integrator = EnterpriseMCPIntegrator(
|
76
|
-
user_profile=profile,
|
77
|
-
console_instance=self.console
|
78
|
-
)
|
65
|
+
self.mcp_integrator = EnterpriseMCPIntegrator(user_profile=profile, console_instance=self.console)
|
79
66
|
|
80
67
|
def analyze_vpc_cleanup_candidates(
|
81
68
|
self,
|
82
69
|
vpc_ids: Optional[List[str]] = None,
|
83
70
|
account_profiles: Optional[List[str]] = None,
|
84
71
|
export_results: bool = True,
|
85
|
-
output_directory: str = "./exports/vpc_cleanup"
|
72
|
+
output_directory: str = "./exports/vpc_cleanup",
|
86
73
|
) -> Dict[str, Any]:
|
87
74
|
"""
|
88
75
|
Analyze VPC cleanup candidates with comprehensive dependency analysis
|
89
|
-
|
76
|
+
|
90
77
|
Args:
|
91
78
|
vpc_ids: Specific VPC IDs to analyze
|
92
79
|
account_profiles: Multiple account profiles for multi-account analysis
|
93
80
|
export_results: Export analysis results to files
|
94
81
|
output_directory: Directory for exported files
|
95
|
-
|
82
|
+
|
96
83
|
Returns:
|
97
84
|
Dictionary with analysis results and recommendations
|
98
85
|
"""
|
99
86
|
print_header("VPC Cleanup Analysis", "Enterprise Framework")
|
100
|
-
|
87
|
+
|
101
88
|
# Profile validation
|
102
89
|
if account_profiles:
|
103
90
|
validated_profiles = []
|
@@ -109,393 +96,366 @@ class VPCCleanupCLI:
|
|
109
96
|
print_success(f"Profile validated: {profile_candidate}")
|
110
97
|
except Exception as e:
|
111
98
|
print_error(f"Profile validation failed: {profile_candidate} - {e}")
|
112
|
-
|
99
|
+
|
113
100
|
if not validated_profiles:
|
114
101
|
print_error("No valid profiles available for analysis")
|
115
102
|
return {}
|
116
|
-
|
103
|
+
|
117
104
|
account_profiles = validated_profiles
|
118
|
-
|
105
|
+
|
119
106
|
# Perform analysis
|
120
107
|
try:
|
121
108
|
candidates = self.cleanup_framework.analyze_vpc_cleanup_candidates(
|
122
|
-
vpc_ids=vpc_ids,
|
123
|
-
account_profiles=account_profiles
|
109
|
+
vpc_ids=vpc_ids, account_profiles=account_profiles
|
124
110
|
)
|
125
|
-
|
111
|
+
|
126
112
|
if not candidates:
|
127
113
|
print_warning("No VPC cleanup candidates found")
|
128
114
|
return {}
|
129
|
-
|
115
|
+
|
130
116
|
# Generate cleanup plan
|
131
117
|
cleanup_plan = self.cleanup_framework.generate_cleanup_plan(candidates)
|
132
|
-
|
118
|
+
|
133
119
|
# MCP Cross-Validation: Verify VPC data against real AWS APIs
|
134
120
|
vpc_validation_data = {
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
121
|
+
"vpc_candidates": candidates,
|
122
|
+
"total_vpcs": len(candidates),
|
123
|
+
"regions": [self.region],
|
124
|
+
"profile": self.profile,
|
139
125
|
}
|
140
|
-
|
126
|
+
|
141
127
|
print_warning("Performing MCP cross-validation against AWS APIs...")
|
142
128
|
try:
|
143
129
|
# Cross-validate VPC discovery and dependencies
|
144
130
|
import asyncio
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
131
|
+
|
132
|
+
mcp_result = asyncio.run(self.mcp_integrator.validate_vpc_operations(vpc_validation_data))
|
133
|
+
|
149
134
|
if mcp_result.success and mcp_result.consistency_score >= 99.5:
|
150
135
|
actual_vpc_count = mcp_result.total_resources_validated
|
151
136
|
consistency_score = mcp_result.consistency_score
|
152
|
-
|
137
|
+
|
153
138
|
print_success(
|
154
139
|
f"✅ MCP Validation: {consistency_score:.1f}% accuracy - "
|
155
140
|
f"Found {actual_vpc_count} VPCs vs {len(candidates)} candidates"
|
156
141
|
)
|
157
|
-
|
142
|
+
|
158
143
|
# Add MCP validation results to cleanup plan
|
159
|
-
cleanup_plan[
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
144
|
+
cleanup_plan["mcp_validation"] = {
|
145
|
+
"validated": True,
|
146
|
+
"consistency_score": consistency_score,
|
147
|
+
"actual_vpc_count": actual_vpc_count,
|
148
|
+
"validation_timestamp": mcp_result.validation_timestamp,
|
164
149
|
}
|
165
150
|
else:
|
166
151
|
print_error(f"❌ MCP Validation failed: {mcp_result.consistency_score:.1f}% accuracy")
|
167
|
-
cleanup_plan[
|
168
|
-
|
169
|
-
'errors': mcp_result.error_details
|
170
|
-
}
|
171
|
-
|
152
|
+
cleanup_plan["mcp_validation"] = {"validated": False, "errors": mcp_result.error_details}
|
153
|
+
|
172
154
|
except Exception as e:
|
173
155
|
print_error(f"MCP cross-validation error: {e}")
|
174
|
-
cleanup_plan[
|
175
|
-
|
156
|
+
cleanup_plan["mcp_validation"] = {"validated": False, "error": str(e)}
|
157
|
+
|
176
158
|
# Display results
|
177
159
|
self.cleanup_framework.display_cleanup_analysis(candidates)
|
178
|
-
|
160
|
+
|
179
161
|
# Display executive summary
|
180
162
|
self._display_executive_summary(cleanup_plan)
|
181
|
-
|
163
|
+
|
182
164
|
# Export results if requested
|
183
165
|
exported_files = {}
|
184
166
|
if export_results:
|
185
167
|
exported_files = self.cleanup_framework.export_cleanup_plan(
|
186
|
-
output_directory=output_directory,
|
187
|
-
include_dependencies=True
|
168
|
+
output_directory=output_directory, include_dependencies=True
|
188
169
|
)
|
189
|
-
|
170
|
+
|
190
171
|
return {
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
}
|
172
|
+
"candidates": candidates,
|
173
|
+
"cleanup_plan": cleanup_plan,
|
174
|
+
"exported_files": exported_files,
|
175
|
+
"analysis_summary": {
|
176
|
+
"total_vpcs": len(candidates),
|
177
|
+
"immediate_cleanup": len([c for c in candidates if c.cleanup_phase == VPCCleanupPhase.IMMEDIATE]),
|
178
|
+
"total_annual_savings": sum((c.annual_savings or 0.0) for c in candidates),
|
179
|
+
"safety_mode_enabled": self.safety_mode,
|
180
|
+
},
|
200
181
|
}
|
201
|
-
|
182
|
+
|
202
183
|
except Exception as e:
|
203
184
|
print_error(f"VPC cleanup analysis failed: {e}")
|
204
185
|
logger.error(f"VPC cleanup analysis error: {e}")
|
205
186
|
return {}
|
206
187
|
|
207
188
|
def execute_cleanup_phase(
|
208
|
-
self,
|
209
|
-
phase: str,
|
210
|
-
vpc_ids: Optional[List[str]] = None,
|
211
|
-
dry_run: bool = True,
|
212
|
-
require_approval: bool = True
|
189
|
+
self, phase: str, vpc_ids: Optional[List[str]] = None, dry_run: bool = True, require_approval: bool = True
|
213
190
|
) -> Dict[str, Any]:
|
214
191
|
"""
|
215
192
|
Execute VPC cleanup for a specific phase
|
216
|
-
|
193
|
+
|
217
194
|
Args:
|
218
195
|
phase: Cleanup phase to execute (immediate, investigation, governance, complex)
|
219
196
|
vpc_ids: Specific VPC IDs to clean up
|
220
197
|
dry_run: Execute in dry-run mode only
|
221
198
|
require_approval: Require explicit user approval
|
222
|
-
|
199
|
+
|
223
200
|
Returns:
|
224
201
|
Dictionary with execution results
|
225
202
|
"""
|
226
203
|
print_header(f"VPC Cleanup Execution - {phase.title()} Phase", "Enterprise Safety Controls")
|
227
|
-
|
204
|
+
|
228
205
|
if not self.cleanup_framework.cleanup_candidates:
|
229
206
|
print_error("No VPC candidates available. Run analysis first.")
|
230
207
|
return {}
|
231
|
-
|
208
|
+
|
232
209
|
# Map phase string to enum
|
233
210
|
phase_mapping = {
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
211
|
+
"immediate": VPCCleanupPhase.IMMEDIATE,
|
212
|
+
"investigation": VPCCleanupPhase.INVESTIGATION,
|
213
|
+
"governance": VPCCleanupPhase.GOVERNANCE,
|
214
|
+
"complex": VPCCleanupPhase.COMPLEX,
|
238
215
|
}
|
239
|
-
|
216
|
+
|
240
217
|
cleanup_phase = phase_mapping.get(phase.lower())
|
241
218
|
if not cleanup_phase:
|
242
219
|
print_error(f"Invalid cleanup phase: {phase}")
|
243
220
|
return {}
|
244
|
-
|
221
|
+
|
245
222
|
# Filter candidates for this phase
|
246
|
-
phase_candidates = [
|
247
|
-
|
248
|
-
if c.cleanup_phase == cleanup_phase
|
249
|
-
]
|
250
|
-
|
223
|
+
phase_candidates = [c for c in self.cleanup_framework.cleanup_candidates if c.cleanup_phase == cleanup_phase]
|
224
|
+
|
251
225
|
if vpc_ids:
|
252
226
|
phase_candidates = [c for c in phase_candidates if c.vpc_id in vpc_ids]
|
253
|
-
|
227
|
+
|
254
228
|
if not phase_candidates:
|
255
229
|
print_warning(f"No VPC candidates found for {phase} phase")
|
256
230
|
return {}
|
257
|
-
|
231
|
+
|
258
232
|
# Safety checks
|
259
233
|
if self.safety_mode and not dry_run:
|
260
234
|
print_warning("Safety mode is enabled. Forced dry-run execution.")
|
261
235
|
dry_run = True
|
262
|
-
|
236
|
+
|
263
237
|
# Display execution plan
|
264
238
|
self._display_execution_plan(phase_candidates, dry_run)
|
265
|
-
|
239
|
+
|
266
240
|
# Require approval for non-dry-run execution
|
267
241
|
if not dry_run and require_approval:
|
268
242
|
approval_message = (
|
269
243
|
f"You are about to execute VPC cleanup for {len(phase_candidates)} VPCs.\n"
|
270
244
|
f"This action cannot be undone. Are you sure you want to proceed?"
|
271
245
|
)
|
272
|
-
|
246
|
+
|
273
247
|
if not Confirm.ask(approval_message, default=False):
|
274
248
|
print_warning("VPC cleanup execution cancelled by user")
|
275
|
-
return {
|
276
|
-
|
249
|
+
return {"status": "cancelled", "reason": "user_cancellation"}
|
250
|
+
|
277
251
|
# Execute cleanup (currently dry-run only for safety)
|
278
252
|
execution_results = {
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
253
|
+
"phase": phase,
|
254
|
+
"vpc_count": len(phase_candidates),
|
255
|
+
"dry_run": True, # Force dry-run for safety
|
256
|
+
"execution_plan": [],
|
257
|
+
"warnings": [],
|
258
|
+
"recommendations": [],
|
285
259
|
}
|
286
|
-
|
260
|
+
|
287
261
|
for candidate in phase_candidates:
|
288
262
|
vpc_plan = self._generate_vpc_deletion_plan(candidate)
|
289
|
-
execution_results[
|
290
|
-
|
263
|
+
execution_results["execution_plan"].append(vpc_plan)
|
264
|
+
|
291
265
|
# Safety warnings
|
292
266
|
if (candidate.blocking_dependencies or 0) > 0:
|
293
|
-
execution_results[
|
267
|
+
execution_results["warnings"].append(
|
294
268
|
f"VPC {candidate.vpc_id} has {candidate.blocking_dependencies or 0} blocking dependencies"
|
295
269
|
)
|
296
|
-
|
270
|
+
|
297
271
|
if candidate.is_default:
|
298
|
-
execution_results[
|
272
|
+
execution_results["warnings"].append(
|
299
273
|
f"VPC {candidate.vpc_id} is a default VPC - requires platform approval"
|
300
274
|
)
|
301
|
-
|
275
|
+
|
302
276
|
# Generate recommendations
|
303
|
-
execution_results[
|
304
|
-
|
277
|
+
execution_results["recommendations"] = self._generate_execution_recommendations(phase_candidates)
|
278
|
+
|
305
279
|
print_success(f"VPC cleanup plan generated for {len(phase_candidates)} VPCs")
|
306
|
-
|
280
|
+
|
307
281
|
if dry_run:
|
308
282
|
print_warning("Dry-run mode: No actual VPC deletions performed")
|
309
|
-
|
283
|
+
|
310
284
|
return execution_results
|
311
285
|
|
312
286
|
def generate_business_report(
|
313
|
-
self,
|
314
|
-
include_executive_summary: bool = True,
|
315
|
-
export_formats: Optional[List[str]] = None
|
287
|
+
self, include_executive_summary: bool = True, export_formats: Optional[List[str]] = None
|
316
288
|
) -> Dict[str, Any]:
|
317
289
|
"""
|
318
290
|
Generate business-focused VPC cleanup report
|
319
|
-
|
291
|
+
|
320
292
|
Args:
|
321
293
|
include_executive_summary: Include executive summary
|
322
294
|
export_formats: Export formats (json, csv, html)
|
323
|
-
|
295
|
+
|
324
296
|
Returns:
|
325
297
|
Dictionary with business report and export information
|
326
298
|
"""
|
327
299
|
print_header("VPC Cleanup Business Report", "Executive Dashboard")
|
328
|
-
|
300
|
+
|
329
301
|
if not self.cleanup_framework.cleanup_candidates:
|
330
302
|
print_error("No VPC analysis data available. Run analysis first.")
|
331
303
|
return {}
|
332
|
-
|
304
|
+
|
333
305
|
if not export_formats:
|
334
|
-
export_formats = [
|
335
|
-
|
306
|
+
export_formats = ["json", "csv"]
|
307
|
+
|
336
308
|
try:
|
337
309
|
# Configure manager interface for business reporting
|
338
310
|
self.manager_interface.configure_for_business_user(
|
339
311
|
safety_mode=self.safety_mode,
|
340
312
|
target_savings=30.0, # 30% cost reduction target
|
341
|
-
approval_threshold=1000.0 # $1K approval threshold
|
313
|
+
approval_threshold=1000.0, # $1K approval threshold
|
342
314
|
)
|
343
|
-
|
315
|
+
|
344
316
|
# Convert technical analysis to business insights
|
345
317
|
vpc_analysis_results = {
|
346
|
-
|
347
|
-
|
318
|
+
"vpc_candidates": self.cleanup_framework.cleanup_candidates,
|
319
|
+
"cleanup_plan": self.cleanup_framework.analysis_results,
|
348
320
|
}
|
349
|
-
|
350
|
-
business_analysis = self.manager_interface.analyze_cost_optimization_opportunity(
|
351
|
-
|
352
|
-
)
|
353
|
-
|
321
|
+
|
322
|
+
business_analysis = self.manager_interface.analyze_cost_optimization_opportunity(vpc_analysis_results)
|
323
|
+
|
354
324
|
# Display business dashboard
|
355
325
|
if include_executive_summary:
|
356
326
|
self.manager_interface.display_business_dashboard()
|
357
|
-
|
327
|
+
|
358
328
|
# Export business reports
|
359
329
|
exported_files = self.manager_interface.export_manager_friendly_reports()
|
360
|
-
|
330
|
+
|
361
331
|
return {
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
332
|
+
"business_analysis": business_analysis,
|
333
|
+
"recommendations": self.manager_interface.business_recommendations,
|
334
|
+
"executive_presentation": self.manager_interface.generate_executive_presentation(),
|
335
|
+
"exported_files": exported_files,
|
366
336
|
}
|
367
|
-
|
337
|
+
|
368
338
|
except Exception as e:
|
369
339
|
print_error(f"Business report generation failed: {e}")
|
370
340
|
logger.error(f"Business report error: {e}")
|
371
341
|
return {}
|
372
342
|
|
373
|
-
def validate_vpc_cleanup_safety(
|
374
|
-
self,
|
375
|
-
vpc_id: str,
|
376
|
-
account_profile: Optional[str] = None
|
377
|
-
) -> Dict[str, Any]:
|
343
|
+
def validate_vpc_cleanup_safety(self, vpc_id: str, account_profile: Optional[str] = None) -> Dict[str, Any]:
|
378
344
|
"""
|
379
345
|
Validate VPC cleanup safety with comprehensive dependency checking
|
380
|
-
|
346
|
+
|
381
347
|
Args:
|
382
348
|
vpc_id: VPC ID to validate
|
383
349
|
account_profile: AWS profile for the account containing the VPC
|
384
|
-
|
350
|
+
|
385
351
|
Returns:
|
386
352
|
Dictionary with safety validation results
|
387
353
|
"""
|
388
354
|
print_header(f"VPC Safety Validation", vpc_id)
|
389
|
-
|
355
|
+
|
390
356
|
# Find the VPC candidate
|
391
357
|
vpc_candidate = None
|
392
358
|
for candidate in self.cleanup_framework.cleanup_candidates:
|
393
359
|
if candidate.vpc_id == vpc_id:
|
394
360
|
vpc_candidate = candidate
|
395
361
|
break
|
396
|
-
|
362
|
+
|
397
363
|
if not vpc_candidate:
|
398
364
|
# Run targeted analysis for this VPC
|
399
365
|
profile_to_use = account_profile or self.profile
|
400
|
-
|
366
|
+
|
401
367
|
temp_framework = VPCCleanupFramework(
|
402
|
-
profile=profile_to_use,
|
403
|
-
region=self.region,
|
404
|
-
console=self.console,
|
405
|
-
safety_mode=True
|
368
|
+
profile=profile_to_use, region=self.region, console=self.console, safety_mode=True
|
406
369
|
)
|
407
|
-
|
370
|
+
|
408
371
|
candidates = temp_framework.analyze_vpc_cleanup_candidates(vpc_ids=[vpc_id])
|
409
|
-
|
372
|
+
|
410
373
|
if candidates:
|
411
374
|
vpc_candidate = candidates[0]
|
412
375
|
else:
|
413
376
|
print_error(f"VPC {vpc_id} not found or inaccessible")
|
414
377
|
return {}
|
415
|
-
|
378
|
+
|
416
379
|
# Perform safety validation
|
417
380
|
safety_results = {
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
381
|
+
"vpc_id": vpc_id,
|
382
|
+
"safety_score": "SAFE",
|
383
|
+
"blocking_dependencies": vpc_candidate.blocking_dependencies or 0,
|
384
|
+
"risk_level": vpc_candidate.risk_level.value,
|
385
|
+
"safety_checks": [],
|
386
|
+
"warnings": [],
|
387
|
+
"approval_required": vpc_candidate.approval_required,
|
425
388
|
}
|
426
|
-
|
389
|
+
|
427
390
|
# ENI check (most critical)
|
428
391
|
if vpc_candidate.eni_count > 0:
|
429
|
-
safety_results[
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
392
|
+
safety_results["safety_checks"].append(
|
393
|
+
{
|
394
|
+
"check": "ENI Count",
|
395
|
+
"status": "FAIL",
|
396
|
+
"details": f"{vpc_candidate.eni_count} network interfaces found",
|
397
|
+
"blocking": True,
|
398
|
+
}
|
399
|
+
)
|
400
|
+
safety_results["safety_score"] = "UNSAFE"
|
436
401
|
else:
|
437
|
-
safety_results[
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
'blocking': False
|
442
|
-
})
|
443
|
-
|
402
|
+
safety_results["safety_checks"].append(
|
403
|
+
{"check": "ENI Count", "status": "PASS", "details": "No active network interfaces", "blocking": False}
|
404
|
+
)
|
405
|
+
|
444
406
|
# Dependency checks
|
445
407
|
internal_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 1])
|
446
408
|
external_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 2])
|
447
409
|
control_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 3])
|
448
|
-
|
449
|
-
safety_results[
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
410
|
+
|
411
|
+
safety_results["safety_checks"].extend(
|
412
|
+
[
|
413
|
+
{
|
414
|
+
"check": "Internal Dependencies",
|
415
|
+
"status": "WARN" if internal_deps > 0 else "PASS",
|
416
|
+
"details": f"{internal_deps} internal dependencies (NAT, Endpoints, etc.)",
|
417
|
+
"blocking": internal_deps > 0,
|
418
|
+
},
|
419
|
+
{
|
420
|
+
"check": "External Dependencies",
|
421
|
+
"status": "WARN" if external_deps > 0 else "PASS",
|
422
|
+
"details": f"{external_deps} external dependencies (TGW, Peering, etc.)",
|
423
|
+
"blocking": external_deps > 0,
|
424
|
+
},
|
425
|
+
{
|
426
|
+
"check": "Control Plane Dependencies",
|
427
|
+
"status": "WARN" if control_deps > 0 else "PASS",
|
428
|
+
"details": f"{control_deps} control plane dependencies",
|
429
|
+
"blocking": control_deps > 0,
|
430
|
+
},
|
431
|
+
]
|
432
|
+
)
|
433
|
+
|
470
434
|
# Update safety score based on blocking dependencies
|
471
|
-
blocking_checks = len([c for c in safety_results[
|
435
|
+
blocking_checks = len([c for c in safety_results["safety_checks"] if c["blocking"]])
|
472
436
|
if blocking_checks > 0:
|
473
|
-
safety_results[
|
474
|
-
|
437
|
+
safety_results["safety_score"] = "UNSAFE"
|
438
|
+
|
475
439
|
# IaC management check
|
476
440
|
if vpc_candidate.iac_managed:
|
477
|
-
safety_results[
|
478
|
-
|
479
|
-
)
|
480
|
-
|
441
|
+
safety_results["warnings"].append(f"VPC is managed by Infrastructure as Code: {vpc_candidate.iac_source}")
|
442
|
+
|
481
443
|
# Default VPC check
|
482
444
|
if vpc_candidate.is_default:
|
483
|
-
safety_results[
|
484
|
-
|
485
|
-
)
|
486
|
-
|
445
|
+
safety_results["warnings"].append("VPC is a default VPC - requires platform team approval")
|
446
|
+
|
487
447
|
# Display results
|
488
448
|
self._display_safety_validation(safety_results)
|
489
|
-
|
449
|
+
|
490
450
|
return safety_results
|
491
451
|
|
492
452
|
def _display_executive_summary(self, cleanup_plan: Dict[str, Any]) -> None:
|
493
453
|
"""Display executive summary of cleanup plan"""
|
494
454
|
if not cleanup_plan:
|
495
455
|
return
|
496
|
-
|
497
|
-
exec_summary = cleanup_plan.get(
|
498
|
-
|
456
|
+
|
457
|
+
exec_summary = cleanup_plan.get("executive_summary", {})
|
458
|
+
|
499
459
|
summary_text = (
|
500
460
|
f"[bold blue]📊 EXECUTIVE SUMMARY[/bold blue]\n\n"
|
501
461
|
f"Total VPCs Analyzed: [yellow]{cleanup_plan['metadata']['total_vpcs_analyzed']}[/yellow]\n"
|
@@ -507,13 +467,13 @@ class VPCCleanupCLI:
|
|
507
467
|
f"Total Annual Savings: [bold green]${(cleanup_plan['metadata']['total_annual_savings'] or 0.0):,.2f}[/bold green]\n"
|
508
468
|
f"Business Case Strength: [cyan]{exec_summary.get('business_case_strength', 'Unknown')}[/cyan]"
|
509
469
|
)
|
510
|
-
|
470
|
+
|
511
471
|
self.console.print(Panel(summary_text, title="Executive Summary", style="white", width=80))
|
512
472
|
|
513
473
|
def _display_execution_plan(self, candidates: List, dry_run: bool) -> None:
|
514
474
|
"""Display VPC cleanup execution plan"""
|
515
475
|
mode_text = "[yellow]DRY RUN MODE[/yellow]" if dry_run else "[red]LIVE EXECUTION MODE[/red]"
|
516
|
-
|
476
|
+
|
517
477
|
plan_text = (
|
518
478
|
f"[bold blue]🚀 EXECUTION PLAN[/bold blue]\n\n"
|
519
479
|
f"Mode: {mode_text}\n"
|
@@ -522,7 +482,7 @@ class VPCCleanupCLI:
|
|
522
482
|
f"High Risk VPCs: [red]{len([c for c in candidates if c.risk_level == VPCCleanupRisk.HIGH])}[/red]\n"
|
523
483
|
f"Default VPCs: [magenta]{len([c for c in candidates if c.is_default])}[/magenta]"
|
524
484
|
)
|
525
|
-
|
485
|
+
|
526
486
|
self.console.print(Panel(plan_text, title="Execution Plan", style="yellow" if dry_run else "red", width=80))
|
527
487
|
|
528
488
|
def _display_safety_validation(self, safety_results: Dict[str, Any]) -> None:
|
@@ -534,122 +494,216 @@ class VPCCleanupCLI:
|
|
534
494
|
{"header": "Check", "style": "cyan"},
|
535
495
|
{"header": "Status", "style": "green"},
|
536
496
|
{"header": "Details", "style": "white"},
|
537
|
-
{"header": "Blocking", "style": "red"}
|
538
|
-
]
|
497
|
+
{"header": "Blocking", "style": "red"},
|
498
|
+
],
|
539
499
|
)
|
540
|
-
|
541
|
-
for check in safety_results[
|
500
|
+
|
501
|
+
for check in safety_results["safety_checks"]:
|
542
502
|
status_color = {
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
}.get(check[
|
547
|
-
|
548
|
-
blocking_indicator = "🔴 YES" if check[
|
549
|
-
|
550
|
-
table.add_row(
|
551
|
-
|
552
|
-
status_color,
|
553
|
-
check['details'],
|
554
|
-
blocking_indicator
|
555
|
-
)
|
556
|
-
|
503
|
+
"PASS": "[green]✅ PASS[/green]",
|
504
|
+
"WARN": "[yellow]⚠️ WARN[/yellow]",
|
505
|
+
"FAIL": "[red]❌ FAIL[/red]",
|
506
|
+
}.get(check["status"], check["status"])
|
507
|
+
|
508
|
+
blocking_indicator = "🔴 YES" if check["blocking"] else "✅ NO"
|
509
|
+
|
510
|
+
table.add_row(check["check"], status_color, check["details"], blocking_indicator)
|
511
|
+
|
557
512
|
self.console.print(table)
|
558
|
-
|
513
|
+
|
559
514
|
# Overall safety assessment
|
560
|
-
safety_color = "green" if safety_results[
|
515
|
+
safety_color = "green" if safety_results["safety_score"] == "SAFE" else "red"
|
561
516
|
assessment_text = (
|
562
517
|
f"[bold {safety_color}]Overall Safety: {safety_results['safety_score']}[/bold {safety_color}]\n"
|
563
518
|
f"Risk Level: [magenta]{safety_results['risk_level']}[/magenta]\n"
|
564
519
|
f"Approval Required: [yellow]{'YES' if safety_results['approval_required'] else 'NO'}[/yellow]"
|
565
520
|
)
|
566
|
-
|
521
|
+
|
567
522
|
self.console.print(Panel(assessment_text, title="Safety Assessment", style=safety_color, width=60))
|
568
|
-
|
523
|
+
|
569
524
|
# Display warnings
|
570
|
-
if safety_results[
|
571
|
-
warnings_text = "\n".join([f"⚠️ {warning}" for warning in safety_results[
|
525
|
+
if safety_results["warnings"]:
|
526
|
+
warnings_text = "\n".join([f"⚠️ {warning}" for warning in safety_results["warnings"]])
|
572
527
|
self.console.print(Panel(warnings_text, title="Important Warnings", style="yellow", width=80))
|
573
528
|
|
574
529
|
def _generate_vpc_deletion_plan(self, candidate) -> Dict[str, Any]:
|
575
530
|
"""Generate detailed VPC deletion plan"""
|
576
531
|
deletion_steps = []
|
577
|
-
|
532
|
+
|
578
533
|
# Sort dependencies by deletion order
|
579
534
|
sorted_deps = sorted(candidate.dependencies, key=lambda x: x.deletion_order)
|
580
|
-
|
535
|
+
|
581
536
|
for i, dep in enumerate(sorted_deps, 1):
|
582
|
-
deletion_steps.append(
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
537
|
+
deletion_steps.append(
|
538
|
+
{
|
539
|
+
"step": i,
|
540
|
+
"action": f"Delete {dep.resource_type}",
|
541
|
+
"resource_id": dep.resource_id,
|
542
|
+
"api_method": dep.api_method,
|
543
|
+
"description": dep.description,
|
544
|
+
"dependency_level": dep.dependency_level,
|
545
|
+
}
|
546
|
+
)
|
547
|
+
|
591
548
|
# Final VPC deletion step
|
592
|
-
deletion_steps.append(
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
549
|
+
deletion_steps.append(
|
550
|
+
{
|
551
|
+
"step": len(deletion_steps) + 1,
|
552
|
+
"action": "Delete VPC",
|
553
|
+
"resource_id": candidate.vpc_id,
|
554
|
+
"api_method": "delete_vpc",
|
555
|
+
"description": "Final VPC deletion",
|
556
|
+
"dependency_level": 0,
|
557
|
+
}
|
558
|
+
)
|
559
|
+
|
601
560
|
return {
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
561
|
+
"vpc_id": candidate.vpc_id,
|
562
|
+
"vpc_name": candidate.vpc_name,
|
563
|
+
"risk_level": candidate.risk_level.value,
|
564
|
+
"total_steps": len(deletion_steps),
|
565
|
+
"estimated_time": f"{len(deletion_steps) * 2} minutes",
|
566
|
+
"deletion_steps": deletion_steps,
|
608
567
|
}
|
609
568
|
|
610
569
|
def _generate_execution_recommendations(self, candidates: List) -> List[str]:
|
611
570
|
"""Generate execution recommendations"""
|
612
571
|
recommendations = []
|
613
|
-
|
572
|
+
|
614
573
|
# Phase-specific recommendations
|
615
574
|
immediate_count = len([c for c in candidates if c.cleanup_phase == VPCCleanupPhase.IMMEDIATE])
|
616
575
|
high_risk_count = len([c for c in candidates if c.risk_level == VPCCleanupRisk.HIGH])
|
617
576
|
default_vpc_count = len([c for c in candidates if c.is_default])
|
618
577
|
iac_managed_count = len([c for c in candidates if c.iac_managed])
|
619
|
-
|
578
|
+
|
620
579
|
if immediate_count > 0:
|
621
|
-
recommendations.append(
|
622
|
-
|
623
|
-
)
|
624
|
-
|
580
|
+
recommendations.append(f"Execute {immediate_count} immediate cleanup candidates first for quick wins")
|
581
|
+
|
625
582
|
if high_risk_count > 0:
|
626
|
-
recommendations.append(
|
627
|
-
|
628
|
-
)
|
629
|
-
|
583
|
+
recommendations.append(f"Review {high_risk_count} high-risk VPCs with stakeholders before execution")
|
584
|
+
|
630
585
|
if default_vpc_count > 0:
|
631
|
-
recommendations.append(
|
632
|
-
|
633
|
-
)
|
634
|
-
|
586
|
+
recommendations.append(f"Obtain platform team approval for {default_vpc_count} default VPC deletions")
|
587
|
+
|
635
588
|
if iac_managed_count > 0:
|
636
|
-
recommendations.append(
|
637
|
-
|
638
|
-
)
|
639
|
-
|
589
|
+
recommendations.append(f"Update Infrastructure as Code for {iac_managed_count} IaC-managed VPCs")
|
590
|
+
|
640
591
|
# General recommendations
|
641
|
-
recommendations.extend(
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
592
|
+
recommendations.extend(
|
593
|
+
[
|
594
|
+
"Execute VPC cleanup in phases to minimize blast radius",
|
595
|
+
"Validate each deletion step before proceeding to next",
|
596
|
+
"Maintain comprehensive audit trail of all deletion activities",
|
597
|
+
"Schedule cleanup during maintenance windows to minimize impact",
|
598
|
+
]
|
599
|
+
)
|
600
|
+
|
648
601
|
return recommendations
|
649
602
|
|
650
603
|
|
604
|
+
def display_config_campaign_results(results: Dict[str, Any]) -> None:
|
605
|
+
"""
|
606
|
+
Display config-driven campaign results using Rich CLI formatting (NEW FUNCTION)
|
607
|
+
|
608
|
+
This function formats and displays the results from VPCCleanupFramework.analyze_from_config()
|
609
|
+
with enterprise-grade Rich CLI presentation for campaign analysis visibility.
|
610
|
+
|
611
|
+
Args:
|
612
|
+
results: Campaign analysis results from analyze_from_config()
|
613
|
+
"""
|
614
|
+
from runbooks.common.rich_utils import (
|
615
|
+
console,
|
616
|
+
create_table,
|
617
|
+
print_header,
|
618
|
+
print_success,
|
619
|
+
print_warning,
|
620
|
+
print_info,
|
621
|
+
create_panel,
|
622
|
+
)
|
623
|
+
|
624
|
+
# Extract campaign metadata
|
625
|
+
campaign_meta = results.get("campaign_metadata", {})
|
626
|
+
campaign_id = campaign_meta.get("campaign_id", "Unknown")
|
627
|
+
campaign_name = campaign_meta.get("campaign_name", "VPC Cleanup Campaign")
|
628
|
+
aws_profile = campaign_meta.get("aws_billing_profile", "default")
|
629
|
+
execution_date = campaign_meta.get("execution_date", "N/A")
|
630
|
+
|
631
|
+
# Display campaign header
|
632
|
+
print_header(f"Campaign {campaign_id}: {campaign_name}", "Config-Driven Analysis")
|
633
|
+
|
634
|
+
# Campaign metadata panel
|
635
|
+
metadata_content = (
|
636
|
+
f"[bold cyan]Campaign Metadata[/bold cyan]\n\n"
|
637
|
+
f"[white]Campaign ID:[/white] [bright_yellow]{campaign_id}[/bright_yellow]\n"
|
638
|
+
f"[white]AWS Billing Profile:[/white] [bright_blue]{aws_profile}[/bright_blue]\n"
|
639
|
+
f"[white]Execution Date:[/white] [dim]{execution_date}[/dim]\n"
|
640
|
+
f"[white]Description:[/white] {campaign_meta.get('description', 'N/A')}"
|
641
|
+
)
|
642
|
+
|
643
|
+
console.print(create_panel(metadata_content, title="Campaign Information", border_style="cyan"))
|
644
|
+
console.print()
|
645
|
+
|
646
|
+
# VPC results table
|
647
|
+
vpc_results = results.get("vpc_results", [])
|
648
|
+
|
649
|
+
if vpc_results:
|
650
|
+
table = create_table(
|
651
|
+
title=f"VPC Campaign Results - {len(vpc_results)} VPCs Analyzed",
|
652
|
+
columns=[
|
653
|
+
{"name": "VPC ID", "style": "bright_cyan"},
|
654
|
+
{"name": "Account", "style": "bright_blue"},
|
655
|
+
{"name": "Region", "style": "yellow"},
|
656
|
+
{"name": "Deletion Date", "style": "dim"},
|
657
|
+
{"name": "Monthly Savings", "style": "bright_green", "justify": "right"},
|
658
|
+
{"name": "Annual Savings", "style": "bright_green bold", "justify": "right"},
|
659
|
+
{"name": "Confidence", "style": "cyan"},
|
660
|
+
],
|
661
|
+
)
|
662
|
+
|
663
|
+
for vpc_result in vpc_results:
|
664
|
+
table.add_row(
|
665
|
+
vpc_result.get("vpc_id", "N/A"),
|
666
|
+
vpc_result.get("account_id", "N/A"),
|
667
|
+
vpc_result.get("region", "N/A"),
|
668
|
+
vpc_result.get("deletion_date", "N/A"),
|
669
|
+
f"${vpc_result.get('monthly_savings', 0.0):,.2f}",
|
670
|
+
f"${vpc_result.get('annual_savings', 0.0):,.2f}",
|
671
|
+
vpc_result.get("confidence_level", "MEDIUM"),
|
672
|
+
)
|
673
|
+
|
674
|
+
console.print(table)
|
675
|
+
console.print()
|
676
|
+
else:
|
677
|
+
print_warning("No VPC results found in campaign analysis")
|
678
|
+
console.print()
|
679
|
+
|
680
|
+
# Total savings summary panel
|
681
|
+
total_savings = results.get("total_savings", {})
|
682
|
+
monthly_total = total_savings.get("monthly", 0.0)
|
683
|
+
annual_total = total_savings.get("annual", 0.0)
|
684
|
+
|
685
|
+
savings_content = (
|
686
|
+
f"[bold green]💰 Total Realized Savings[/bold green]\n\n"
|
687
|
+
f"[white]Monthly Savings:[/white] [bright_green]${monthly_total:,.2f}[/bright_green]\n"
|
688
|
+
f"[white]Annual Savings:[/white] [bright_green bold]${annual_total:,.2f}[/bright_green bold]\n"
|
689
|
+
f"[white]VPCs Analyzed:[/white] [bright_yellow]{len(vpc_results)}[/bright_yellow]"
|
690
|
+
)
|
691
|
+
|
692
|
+
console.print(create_panel(savings_content, title="Financial Impact", border_style="bright_green"))
|
693
|
+
console.print()
|
694
|
+
|
695
|
+
# Campaign success message
|
696
|
+
if annual_total > 0:
|
697
|
+
print_success(
|
698
|
+
f"Campaign {campaign_id} analysis complete: ${annual_total:,.2f}/year realized savings from {len(vpc_results)} VPCs"
|
699
|
+
)
|
700
|
+
else:
|
701
|
+
print_info(f"Campaign {campaign_id} analysis complete - See results above")
|
702
|
+
|
703
|
+
|
651
704
|
# CLI Command Functions for integration with runbooks CLI
|
652
705
|
|
706
|
+
|
653
707
|
def analyze_cleanup_candidates(
|
654
708
|
profile: Optional[str] = None,
|
655
709
|
vpc_ids: Optional[List[str]] = None,
|
@@ -657,11 +711,12 @@ def analyze_cleanup_candidates(
|
|
657
711
|
region: str = "us-east-1",
|
658
712
|
export_results: bool = True,
|
659
713
|
account_limit: Optional[int] = None,
|
660
|
-
region_limit: Optional[int] = None
|
714
|
+
region_limit: Optional[int] = None,
|
715
|
+
config: Optional[str] = None,
|
661
716
|
) -> Dict[str, Any]:
|
662
717
|
"""
|
663
718
|
CLI function to analyze VPC cleanup candidates
|
664
|
-
|
719
|
+
|
665
720
|
Args:
|
666
721
|
profile: AWS profile for analysis
|
667
722
|
vpc_ids: Specific VPC IDs to analyze
|
@@ -670,275 +725,291 @@ def analyze_cleanup_candidates(
|
|
670
725
|
export_results: Export results to files
|
671
726
|
account_limit: Limit number of accounts to process for faster testing
|
672
727
|
region_limit: Limit number of regions to scan per account
|
673
|
-
|
728
|
+
config: Path to YAML campaign configuration file (NEW)
|
729
|
+
|
674
730
|
Returns:
|
675
731
|
Dictionary with analysis results
|
676
732
|
"""
|
677
733
|
# Determine profile to use
|
678
734
|
operational_profile = get_profile_for_operation("operational", profile)
|
679
|
-
|
735
|
+
|
736
|
+
# NEW: Config-driven campaign analysis
|
737
|
+
if config:
|
738
|
+
cleanup_framework = VPCCleanupFramework(profile=operational_profile, region=region, safety_mode=True)
|
739
|
+
|
740
|
+
results = cleanup_framework.analyze_from_config(config)
|
741
|
+
display_config_campaign_results(results)
|
742
|
+
return results
|
743
|
+
|
680
744
|
# Initialize CLI wrapper
|
681
745
|
cleanup_cli = VPCCleanupCLI(
|
682
746
|
profile=operational_profile,
|
683
747
|
region=region,
|
684
|
-
safety_mode=True # Always enable safety mode
|
748
|
+
safety_mode=True, # Always enable safety mode
|
685
749
|
)
|
686
|
-
|
750
|
+
|
687
751
|
# Handle multi-account analysis
|
688
752
|
account_profiles = None
|
689
753
|
if all_accounts:
|
690
754
|
# Use Organizations API to discover all accounts
|
691
755
|
console.print("[blue]🔍 Discovering organization accounts for multi-account VPC analysis...[/blue]")
|
692
|
-
|
756
|
+
|
693
757
|
try:
|
694
758
|
# Import Organizations discovery functionality from FinOps module
|
695
759
|
from runbooks.finops.aws_client import get_organization_accounts
|
696
760
|
from runbooks.common.profile_utils import create_operational_session, create_management_session
|
697
761
|
from runbooks.vpc.cross_account_session import convert_accounts_to_sessions
|
698
|
-
|
762
|
+
|
699
763
|
# Check for cached Organizations data first (performance optimization)
|
700
|
-
|
764
|
+
|
701
765
|
# Use CENTRALISED_OPS_PROFILE if available for operational accounts
|
702
766
|
import os
|
703
|
-
|
767
|
+
|
768
|
+
centralised_ops_profile = os.getenv("CENTRALISED_OPS_PROFILE")
|
704
769
|
if centralised_ops_profile:
|
705
770
|
console.print(f"[green]✅ Using CENTRALISED_OPS_PROFILE: {centralised_ops_profile}[/green]")
|
706
771
|
from .mcp_no_eni_validator import _get_cached_organizations_data, _cache_organizations_data
|
707
|
-
|
772
|
+
|
708
773
|
org_accounts = _get_cached_organizations_data()
|
709
|
-
|
774
|
+
|
710
775
|
if not org_accounts:
|
711
776
|
# Create management session for Organizations discovery (needs Organizations permissions)
|
712
|
-
session = create_management_session(
|
713
|
-
|
777
|
+
session = create_management_session(profile_name=operational_profile)
|
778
|
+
|
714
779
|
# Discover all organization accounts
|
715
780
|
org_accounts = get_organization_accounts(session, operational_profile)
|
716
|
-
|
781
|
+
|
717
782
|
# Cache the results for future use (prevents duplicate calls)
|
718
783
|
if org_accounts:
|
719
784
|
_cache_organizations_data(org_accounts)
|
720
|
-
|
785
|
+
|
721
786
|
if org_accounts:
|
722
787
|
# Apply account limit for performance optimization before session creation
|
723
788
|
if account_limit and account_limit < len(org_accounts):
|
724
789
|
console.print(f"[yellow]🎯 Performance mode: limiting to first {account_limit} accounts[/yellow]")
|
725
790
|
org_accounts = org_accounts[:account_limit]
|
726
|
-
|
791
|
+
|
727
792
|
# Convert accounts to cross-account sessions using STS AssumeRole
|
728
793
|
account_sessions, account_metadata = convert_accounts_to_sessions(org_accounts, operational_profile)
|
729
|
-
|
794
|
+
|
730
795
|
console.print(f"[green]✅ Discovered {len(org_accounts)} organization accounts[/green]")
|
731
|
-
console.print(
|
732
|
-
|
796
|
+
console.print(
|
797
|
+
f"[cyan]📋 Created {len(account_sessions)} cross-account sessions for VPC analysis[/cyan]"
|
798
|
+
)
|
799
|
+
|
733
800
|
# Log account discovery for transparency
|
734
801
|
active_count = len([acc for acc in org_accounts if acc.get("status") == "ACTIVE"])
|
735
802
|
inactive_count = len(org_accounts) - active_count
|
736
|
-
console.print(
|
737
|
-
|
803
|
+
console.print(
|
804
|
+
f"[dim]Organization scope: {active_count} active, {inactive_count} inactive accounts[/dim]"
|
805
|
+
)
|
806
|
+
|
738
807
|
# Detect STS AssumeRole failures and switch to multi-profile discovery
|
739
808
|
if len(account_sessions) == 0 and len(org_accounts) > 0:
|
740
|
-
console.print(
|
741
|
-
|
742
|
-
|
809
|
+
console.print(
|
810
|
+
f"[red]❌ STS AssumeRole failed for all {len(org_accounts)} accounts - cross-account access denied[/red]"
|
811
|
+
)
|
812
|
+
console.print(
|
813
|
+
"[yellow]💡 Enhancing to multi-profile discovery for comprehensive VPC scanning[/yellow]"
|
814
|
+
)
|
815
|
+
|
743
816
|
# Enhanced multi-profile discovery pattern (KISS & DRY)
|
744
817
|
console.print("[blue]🔍 Discovering VPC profiles from available AWS configurations...[/blue]")
|
745
818
|
account_profiles = _discover_vpc_profiles_from_available_aws_profiles(operational_profile)
|
746
|
-
|
819
|
+
|
747
820
|
if account_profiles and len(account_profiles) > 1:
|
748
|
-
console.print(
|
821
|
+
console.print(
|
822
|
+
f"[green]✅ Enhanced discovery found {len(account_profiles)} profiles with VPC access[/green]"
|
823
|
+
)
|
749
824
|
else:
|
750
825
|
console.print("[yellow]⚠️ Enhanced discovery fallback to single profile[/yellow]")
|
751
826
|
else:
|
752
827
|
# Store sessions for VPC discovery instead of profiles
|
753
828
|
account_profiles = account_sessions # Pass sessions instead of profile strings
|
754
|
-
|
829
|
+
|
755
830
|
else:
|
756
831
|
console.print("[yellow]⚠️ No organization accounts found, falling back to single profile[/yellow]")
|
757
832
|
account_profiles = [operational_profile] if operational_profile else None
|
758
|
-
|
833
|
+
|
759
834
|
except ImportError as e:
|
760
835
|
console.print(f"[red]❌ Organizations discovery unavailable: {e}[/red]")
|
761
836
|
console.print("[yellow]💡 Falling back to single profile analysis[/yellow]")
|
762
837
|
account_profiles = [operational_profile] if operational_profile else None
|
763
|
-
|
838
|
+
|
764
839
|
except Exception as e:
|
765
840
|
console.print(f"[red]❌ Organizations discovery failed: {e}[/red]")
|
766
841
|
console.print("[yellow]💡 Enhancing to multi-profile discovery for comprehensive VPC scanning[/yellow]")
|
767
|
-
|
842
|
+
|
768
843
|
# Enhanced multi-profile discovery pattern (KISS & DRY)
|
769
844
|
console.print("[blue]🔍 Discovering VPC profiles from available AWS configurations...[/blue]")
|
770
845
|
account_profiles = _discover_vpc_profiles_from_available_aws_profiles(operational_profile)
|
771
|
-
|
846
|
+
|
772
847
|
if account_profiles and len(account_profiles) > 1:
|
773
|
-
console.print(
|
848
|
+
console.print(
|
849
|
+
f"[green]✅ Enhanced discovery found {len(account_profiles)} profiles with VPC access[/green]"
|
850
|
+
)
|
774
851
|
else:
|
775
852
|
console.print("[yellow]⚠️ Enhanced discovery fallback to single profile[/yellow]")
|
776
|
-
|
853
|
+
|
777
854
|
return cleanup_cli.analyze_vpc_cleanup_candidates(
|
778
|
-
vpc_ids=vpc_ids,
|
779
|
-
account_profiles=account_profiles,
|
780
|
-
export_results=export_results
|
855
|
+
vpc_ids=vpc_ids, account_profiles=account_profiles, export_results=export_results
|
781
856
|
)
|
782
857
|
|
783
858
|
|
784
|
-
def validate_cleanup_safety(
|
785
|
-
vpc_id: str,
|
786
|
-
profile: Optional[str] = None,
|
787
|
-
region: str = "us-east-1"
|
788
|
-
) -> Dict[str, Any]:
|
859
|
+
def validate_cleanup_safety(vpc_id: str, profile: Optional[str] = None, region: str = "us-east-1") -> Dict[str, Any]:
|
789
860
|
"""
|
790
861
|
CLI function to validate VPC cleanup safety
|
791
|
-
|
862
|
+
|
792
863
|
Args:
|
793
864
|
vpc_id: VPC ID to validate
|
794
865
|
profile: AWS profile
|
795
866
|
region: AWS region
|
796
|
-
|
867
|
+
|
797
868
|
Returns:
|
798
869
|
Dictionary with safety validation results
|
799
870
|
"""
|
800
871
|
operational_profile = get_profile_for_operation("operational", profile)
|
801
|
-
|
802
|
-
cleanup_cli = VPCCleanupCLI(
|
803
|
-
|
804
|
-
|
805
|
-
safety_mode=True
|
806
|
-
)
|
807
|
-
|
808
|
-
return cleanup_cli.validate_vpc_cleanup_safety(
|
809
|
-
vpc_id=vpc_id,
|
810
|
-
account_profile=operational_profile
|
811
|
-
)
|
872
|
+
|
873
|
+
cleanup_cli = VPCCleanupCLI(profile=operational_profile, region=region, safety_mode=True)
|
874
|
+
|
875
|
+
return cleanup_cli.validate_vpc_cleanup_safety(vpc_id=vpc_id, account_profile=operational_profile)
|
812
876
|
|
813
877
|
|
814
878
|
def _discover_vpc_profiles_from_available_aws_profiles(primary_profile: str) -> List[str]:
|
815
879
|
"""
|
816
880
|
Enhanced multi-profile discovery for comprehensive VPC scanning across all available AWS profiles.
|
817
|
-
|
881
|
+
|
818
882
|
KISS & DRY approach: Use boto3's available_profiles to discover VPCs across Landing Zone
|
819
883
|
when Organizations API cross-account role assumption fails.
|
820
|
-
|
884
|
+
|
821
885
|
Args:
|
822
886
|
primary_profile: Primary operational profile to include
|
823
|
-
|
887
|
+
|
824
888
|
Returns:
|
825
889
|
List of validated AWS profile names for VPC discovery
|
826
890
|
"""
|
827
891
|
import boto3
|
828
892
|
from rich.progress import Progress, TaskID
|
829
|
-
|
893
|
+
|
830
894
|
console.print("[blue]🔍 Discovering VPC profiles from available AWS configurations...[/blue]")
|
831
|
-
|
895
|
+
|
832
896
|
# Get all available AWS profiles
|
833
897
|
try:
|
834
898
|
session = boto3.Session()
|
835
899
|
available_profiles = session.available_profiles
|
836
|
-
|
900
|
+
|
837
901
|
if not available_profiles:
|
838
902
|
console.print("[yellow]⚠️ No AWS profiles found in configuration[/yellow]")
|
839
903
|
return [primary_profile] if primary_profile else []
|
840
|
-
|
904
|
+
|
841
905
|
console.print(f"[cyan]📋 Found {len(available_profiles)} AWS profiles in configuration[/cyan]")
|
842
|
-
|
906
|
+
|
843
907
|
# Enhanced multi-region discovery for comprehensive Landing Zone coverage
|
844
908
|
# Based on user's confirmed NO-ENI VPCs in: us-east-1, us-west-2, ap-southeast-2
|
845
909
|
regions_to_check = [
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
910
|
+
"us-east-1", # Primary US region - user confirmed VPCs here
|
911
|
+
"us-west-2", # Secondary US region - user confirmed VPCs here
|
912
|
+
"ap-southeast-2", # APAC region - user confirmed VPCs here
|
913
|
+
"eu-west-1", # Europe primary
|
914
|
+
"ca-central-1", # Canada
|
915
|
+
"ap-northeast-1", # Tokyo (common enterprise region)
|
852
916
|
]
|
853
|
-
|
917
|
+
|
854
918
|
# Validate profiles by attempting to create sessions and check VPC access
|
855
919
|
validated_profiles = []
|
856
920
|
profile_vpc_details = {}
|
857
|
-
|
921
|
+
|
858
922
|
with Progress() as progress:
|
859
923
|
profile_task = progress.add_task("🔍 Validating profiles for VPC access...", total=len(available_profiles))
|
860
|
-
|
924
|
+
|
861
925
|
for profile_name in available_profiles:
|
862
926
|
try:
|
863
927
|
# Skip obvious non-VPC profiles but be less restrictive
|
864
|
-
if
|
928
|
+
if "billing" in profile_name.lower() and "readonly" in profile_name.lower():
|
865
929
|
console.print(f"[dim]⏭️ Skipping {profile_name} (billing-only profile)[/dim]")
|
866
930
|
progress.advance(profile_task)
|
867
931
|
continue
|
868
|
-
|
932
|
+
|
869
933
|
# Create test session
|
870
934
|
test_session = boto3.Session(profile_name=profile_name)
|
871
935
|
total_vpcs = 0
|
872
936
|
regions_with_vpcs = []
|
873
|
-
|
937
|
+
|
874
938
|
# Check multiple regions for VPCs (Landing Zone accounts may have VPCs in different regions)
|
875
939
|
for region in regions_to_check:
|
876
940
|
try:
|
877
|
-
ec2_client = test_session.client(
|
941
|
+
ec2_client = test_session.client("ec2", region_name=region)
|
878
942
|
vpc_response = ec2_client.describe_vpcs(MaxResults=10) # Check more VPCs per region
|
879
|
-
region_vpc_count = len(vpc_response.get(
|
880
|
-
|
943
|
+
region_vpc_count = len(vpc_response.get("Vpcs", []))
|
944
|
+
|
881
945
|
if region_vpc_count > 0:
|
882
946
|
total_vpcs += region_vpc_count
|
883
947
|
regions_with_vpcs.append(f"{region}:{region_vpc_count}")
|
884
|
-
|
948
|
+
|
885
949
|
except Exception as region_error:
|
886
950
|
# Log region-specific errors but don't fail the whole profile
|
887
951
|
if "UnauthorizedOperation" not in str(region_error):
|
888
952
|
console.print(f"[dim]⚠️ {profile_name} in {region}: {str(region_error)[:30]}...[/dim]")
|
889
953
|
continue
|
890
|
-
|
954
|
+
|
891
955
|
# Add profile if it has VPCs in any region OR if it's the primary profile
|
892
956
|
if total_vpcs > 0:
|
893
957
|
validated_profiles.append(profile_name)
|
894
|
-
profile_vpc_details[profile_name] = {
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
console.print(f"[green]✅ {profile_name}: {total_vpcs} VPCs across {len(regions_with_vpcs)} regions[/green]")
|
958
|
+
profile_vpc_details[profile_name] = {"total_vpcs": total_vpcs, "regions": regions_with_vpcs}
|
959
|
+
console.print(
|
960
|
+
f"[green]✅ {profile_name}: {total_vpcs} VPCs across {len(regions_with_vpcs)} regions[/green]"
|
961
|
+
)
|
899
962
|
elif profile_name == primary_profile:
|
900
963
|
# Always include primary profile even if no VPCs found
|
901
964
|
validated_profiles.append(profile_name)
|
902
|
-
console.print(
|
965
|
+
console.print(
|
966
|
+
f"[yellow]🔑 {profile_name}: Primary profile (included despite no VPCs found)[/yellow]"
|
967
|
+
)
|
903
968
|
else:
|
904
969
|
console.print(f"[dim]⚪ {profile_name}: No VPCs found in {len(regions_to_check)} regions[/dim]")
|
905
|
-
|
970
|
+
|
906
971
|
except Exception as e:
|
907
972
|
console.print(f"[dim]❌ {profile_name}: Access failed ({str(e)[:50]}...)[/dim]")
|
908
|
-
|
973
|
+
|
909
974
|
progress.advance(profile_task)
|
910
|
-
|
975
|
+
|
911
976
|
# Ensure primary profile is included if it was validated
|
912
977
|
if primary_profile and primary_profile not in validated_profiles:
|
913
978
|
try:
|
914
979
|
# Test primary profile separately
|
915
980
|
test_session = boto3.Session(profile_name=primary_profile)
|
916
|
-
ec2_client = test_session.client(
|
981
|
+
ec2_client = test_session.client("ec2", region_name="us-east-1")
|
917
982
|
ec2_client.describe_vpcs(MaxResults=1)
|
918
983
|
validated_profiles.insert(0, primary_profile) # Add at front
|
919
984
|
console.print(f"[green]✅ Primary profile {primary_profile} added[/green]")
|
920
985
|
except Exception:
|
921
986
|
console.print(f"[yellow]⚠️ Primary profile {primary_profile} validation failed[/yellow]")
|
922
|
-
|
987
|
+
|
923
988
|
# Enhanced VPC discovery summary
|
924
|
-
total_vpcs_found = sum(details.get(
|
989
|
+
total_vpcs_found = sum(details.get("total_vpcs", 0) for details in profile_vpc_details.values())
|
925
990
|
console.print(f"[bold green]🎯 VPC Discovery Ready: {len(validated_profiles)} validated profiles[/bold green]")
|
926
|
-
|
991
|
+
|
927
992
|
if total_vpcs_found > 0:
|
928
|
-
console.print(
|
929
|
-
|
993
|
+
console.print(
|
994
|
+
f"[bold cyan]📊 Total VPCs discovered: {total_vpcs_found} across {len(profile_vpc_details)} accounts[/bold cyan]"
|
995
|
+
)
|
996
|
+
|
930
997
|
# Show detailed breakdown for profiles with VPCs
|
931
998
|
for profile, details in profile_vpc_details.items():
|
932
|
-
if details[
|
933
|
-
regions_str =
|
999
|
+
if details["total_vpcs"] > 0:
|
1000
|
+
regions_str = ", ".join(details["regions"])
|
934
1001
|
console.print(f"[dim] • {profile}: {regions_str}[/dim]")
|
935
1002
|
else:
|
936
|
-
console.print(
|
937
|
-
|
938
|
-
|
939
|
-
|
1003
|
+
console.print(
|
1004
|
+
f"[yellow]⚠️ No VPCs found across {len(validated_profiles)} profiles - Landing Zone accounts may be empty[/yellow]"
|
1005
|
+
)
|
1006
|
+
|
1007
|
+
console.print(
|
1008
|
+
f"[dim]Profiles: {', '.join(validated_profiles[:3])}{'...' if len(validated_profiles) > 3 else ''}[/dim]"
|
1009
|
+
)
|
1010
|
+
|
940
1011
|
return validated_profiles
|
941
|
-
|
1012
|
+
|
942
1013
|
except Exception as e:
|
943
1014
|
console.print(f"[red]❌ Profile discovery failed: {e}[/red]")
|
944
1015
|
console.print(f"[yellow]💡 Falling back to primary profile: {primary_profile}[/yellow]")
|
@@ -946,33 +1017,25 @@ def _discover_vpc_profiles_from_available_aws_profiles(primary_profile: str) ->
|
|
946
1017
|
|
947
1018
|
|
948
1019
|
def generate_business_report(
|
949
|
-
profile: Optional[str] = None,
|
950
|
-
region: str = "us-east-1",
|
951
|
-
export_formats: Optional[List[str]] = None
|
1020
|
+
profile: Optional[str] = None, region: str = "us-east-1", export_formats: Optional[List[str]] = None
|
952
1021
|
) -> Dict[str, Any]:
|
953
1022
|
"""
|
954
1023
|
CLI function to generate business VPC cleanup report
|
955
|
-
|
1024
|
+
|
956
1025
|
Args:
|
957
1026
|
profile: AWS profile
|
958
1027
|
region: AWS region
|
959
1028
|
export_formats: Export formats
|
960
|
-
|
1029
|
+
|
961
1030
|
Returns:
|
962
1031
|
Dictionary with business report
|
963
1032
|
"""
|
964
1033
|
operational_profile = get_profile_for_operation("operational", profile)
|
965
|
-
|
966
|
-
cleanup_cli = VPCCleanupCLI(
|
967
|
-
|
968
|
-
region=region,
|
969
|
-
safety_mode=True
|
970
|
-
)
|
971
|
-
|
1034
|
+
|
1035
|
+
cleanup_cli = VPCCleanupCLI(profile=operational_profile, region=region, safety_mode=True)
|
1036
|
+
|
972
1037
|
# First run analysis if no candidates exist
|
973
1038
|
if not cleanup_cli.cleanup_framework.cleanup_candidates:
|
974
1039
|
cleanup_cli.analyze_vpc_cleanup_candidates()
|
975
|
-
|
976
|
-
return cleanup_cli.generate_business_report(
|
977
|
-
export_formats=export_formats or ['json', 'csv']
|
978
|
-
)
|
1040
|
+
|
1041
|
+
return cleanup_cli.generate_business_report(export_formats=export_formats or ["json", "csv"])
|