runbooks 0.9.7__py3-none-any.whl → 0.9.9__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.
@@ -0,0 +1,757 @@
1
+ """
2
+ VPC Cleanup Wrapper - Enterprise CLI Integration
3
+
4
+ This module provides the CLI wrapper for VPC cleanup operations, integrating
5
+ with the existing runbooks framework and providing enterprise-grade safety
6
+ controls and multi-account support.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import click
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.prompt import Confirm, Prompt
17
+
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
+ )
27
+ from runbooks.common.mcp_integration import EnterpriseMCPIntegrator
28
+ from .vpc_cleanup_integration import VPCCleanupFramework, VPCCleanupPhase, VPCCleanupRisk
29
+ from .manager_interface import VPCManagerInterface
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class VPCCleanupCLI:
35
+ """
36
+ Enterprise VPC Cleanup CLI wrapper with safety controls and approval gates
37
+
38
+ Provides comprehensive VPC cleanup capabilities integrated with the existing
39
+ runbooks framework architecture and enterprise multi-account patterns.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ profile: Optional[str] = None,
45
+ region: str = "us-east-1",
46
+ safety_mode: bool = True,
47
+ console: Optional[Console] = None
48
+ ):
49
+ """
50
+ Initialize VPC Cleanup CLI
51
+
52
+ Args:
53
+ profile: AWS profile for operations
54
+ region: AWS region
55
+ safety_mode: Enable safety controls and approval gates
56
+ console: Rich console for output
57
+ """
58
+ self.profile = profile
59
+ self.region = region
60
+ self.safety_mode = safety_mode
61
+ self.console = console or Console()
62
+
63
+ # Initialize cleanup framework
64
+ self.cleanup_framework = VPCCleanupFramework(
65
+ profile=profile,
66
+ region=region,
67
+ console=self.console,
68
+ safety_mode=safety_mode
69
+ )
70
+
71
+ # Initialize manager interface for business reporting
72
+ self.manager_interface = VPCManagerInterface(console=self.console)
73
+
74
+ # Initialize MCP integrator for cross-validation
75
+ self.mcp_integrator = EnterpriseMCPIntegrator(
76
+ user_profile=profile,
77
+ console_instance=self.console
78
+ )
79
+
80
+ def analyze_vpc_cleanup_candidates(
81
+ self,
82
+ vpc_ids: Optional[List[str]] = None,
83
+ account_profiles: Optional[List[str]] = None,
84
+ export_results: bool = True,
85
+ output_directory: str = "./exports/vpc_cleanup"
86
+ ) -> Dict[str, Any]:
87
+ """
88
+ Analyze VPC cleanup candidates with comprehensive dependency analysis
89
+
90
+ Args:
91
+ vpc_ids: Specific VPC IDs to analyze
92
+ account_profiles: Multiple account profiles for multi-account analysis
93
+ export_results: Export analysis results to files
94
+ output_directory: Directory for exported files
95
+
96
+ Returns:
97
+ Dictionary with analysis results and recommendations
98
+ """
99
+ print_header("VPC Cleanup Analysis", "Enterprise Framework")
100
+
101
+ # Profile validation
102
+ if account_profiles:
103
+ validated_profiles = []
104
+ for profile_candidate in account_profiles:
105
+ try:
106
+ # Validate profile exists and is accessible
107
+ get_profile_for_operation("operational", profile_candidate)
108
+ validated_profiles.append(profile_candidate)
109
+ print_success(f"Profile validated: {profile_candidate}")
110
+ except Exception as e:
111
+ print_error(f"Profile validation failed: {profile_candidate} - {e}")
112
+
113
+ if not validated_profiles:
114
+ print_error("No valid profiles available for analysis")
115
+ return {}
116
+
117
+ account_profiles = validated_profiles
118
+
119
+ # Perform analysis
120
+ try:
121
+ candidates = self.cleanup_framework.analyze_vpc_cleanup_candidates(
122
+ vpc_ids=vpc_ids,
123
+ account_profiles=account_profiles
124
+ )
125
+
126
+ if not candidates:
127
+ print_warning("No VPC cleanup candidates found")
128
+ return {}
129
+
130
+ # Generate cleanup plan
131
+ cleanup_plan = self.cleanup_framework.generate_cleanup_plan(candidates)
132
+
133
+ # MCP Cross-Validation: Verify VPC data against real AWS APIs
134
+ vpc_validation_data = {
135
+ 'vpc_candidates': candidates,
136
+ 'total_vpcs': len(candidates),
137
+ 'regions': [self.region],
138
+ 'profile': self.profile
139
+ }
140
+
141
+ print_warning("Performing MCP cross-validation against AWS APIs...")
142
+ try:
143
+ # Cross-validate VPC discovery and dependencies
144
+ import asyncio
145
+ mcp_result = asyncio.run(
146
+ self.mcp_integrator.validate_vpc_operations(vpc_validation_data)
147
+ )
148
+
149
+ if mcp_result.success and mcp_result.consistency_score >= 99.5:
150
+ actual_vpc_count = mcp_result.total_resources_validated
151
+ consistency_score = mcp_result.consistency_score
152
+
153
+ print_success(
154
+ f"✅ MCP Validation: {consistency_score:.1f}% accuracy - "
155
+ f"Found {actual_vpc_count} VPCs vs {len(candidates)} candidates"
156
+ )
157
+
158
+ # Add MCP validation results to cleanup plan
159
+ cleanup_plan['mcp_validation'] = {
160
+ 'validated': True,
161
+ 'consistency_score': consistency_score,
162
+ 'actual_vpc_count': actual_vpc_count,
163
+ 'validation_timestamp': mcp_result.validation_timestamp
164
+ }
165
+ else:
166
+ print_error(f"❌ MCP Validation failed: {mcp_result.consistency_score:.1f}% accuracy")
167
+ cleanup_plan['mcp_validation'] = {
168
+ 'validated': False,
169
+ 'errors': mcp_result.error_details
170
+ }
171
+
172
+ except Exception as e:
173
+ print_error(f"MCP cross-validation error: {e}")
174
+ cleanup_plan['mcp_validation'] = {'validated': False, 'error': str(e)}
175
+
176
+ # Display results
177
+ self.cleanup_framework.display_cleanup_analysis(candidates)
178
+
179
+ # Display executive summary
180
+ self._display_executive_summary(cleanup_plan)
181
+
182
+ # Export results if requested
183
+ exported_files = {}
184
+ if export_results:
185
+ exported_files = self.cleanup_framework.export_cleanup_plan(
186
+ output_directory=output_directory,
187
+ include_dependencies=True
188
+ )
189
+
190
+ return {
191
+ 'candidates': candidates,
192
+ 'cleanup_plan': cleanup_plan,
193
+ 'exported_files': exported_files,
194
+ 'analysis_summary': {
195
+ 'total_vpcs': len(candidates),
196
+ 'immediate_cleanup': len([c for c in candidates if c.cleanup_phase == VPCCleanupPhase.IMMEDIATE]),
197
+ 'total_annual_savings': sum(c.annual_savings for c in candidates),
198
+ 'safety_mode_enabled': self.safety_mode
199
+ }
200
+ }
201
+
202
+ except Exception as e:
203
+ print_error(f"VPC cleanup analysis failed: {e}")
204
+ logger.error(f"VPC cleanup analysis error: {e}")
205
+ return {}
206
+
207
+ 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
213
+ ) -> Dict[str, Any]:
214
+ """
215
+ Execute VPC cleanup for a specific phase
216
+
217
+ Args:
218
+ phase: Cleanup phase to execute (immediate, investigation, governance, complex)
219
+ vpc_ids: Specific VPC IDs to clean up
220
+ dry_run: Execute in dry-run mode only
221
+ require_approval: Require explicit user approval
222
+
223
+ Returns:
224
+ Dictionary with execution results
225
+ """
226
+ print_header(f"VPC Cleanup Execution - {phase.title()} Phase", "Enterprise Safety Controls")
227
+
228
+ if not self.cleanup_framework.cleanup_candidates:
229
+ print_error("No VPC candidates available. Run analysis first.")
230
+ return {}
231
+
232
+ # Map phase string to enum
233
+ phase_mapping = {
234
+ 'immediate': VPCCleanupPhase.IMMEDIATE,
235
+ 'investigation': VPCCleanupPhase.INVESTIGATION,
236
+ 'governance': VPCCleanupPhase.GOVERNANCE,
237
+ 'complex': VPCCleanupPhase.COMPLEX
238
+ }
239
+
240
+ cleanup_phase = phase_mapping.get(phase.lower())
241
+ if not cleanup_phase:
242
+ print_error(f"Invalid cleanup phase: {phase}")
243
+ return {}
244
+
245
+ # Filter candidates for this phase
246
+ phase_candidates = [
247
+ c for c in self.cleanup_framework.cleanup_candidates
248
+ if c.cleanup_phase == cleanup_phase
249
+ ]
250
+
251
+ if vpc_ids:
252
+ phase_candidates = [c for c in phase_candidates if c.vpc_id in vpc_ids]
253
+
254
+ if not phase_candidates:
255
+ print_warning(f"No VPC candidates found for {phase} phase")
256
+ return {}
257
+
258
+ # Safety checks
259
+ if self.safety_mode and not dry_run:
260
+ print_warning("Safety mode is enabled. Forced dry-run execution.")
261
+ dry_run = True
262
+
263
+ # Display execution plan
264
+ self._display_execution_plan(phase_candidates, dry_run)
265
+
266
+ # Require approval for non-dry-run execution
267
+ if not dry_run and require_approval:
268
+ approval_message = (
269
+ f"You are about to execute VPC cleanup for {len(phase_candidates)} VPCs.\n"
270
+ f"This action cannot be undone. Are you sure you want to proceed?"
271
+ )
272
+
273
+ if not Confirm.ask(approval_message, default=False):
274
+ print_warning("VPC cleanup execution cancelled by user")
275
+ return {'status': 'cancelled', 'reason': 'user_cancellation'}
276
+
277
+ # Execute cleanup (currently dry-run only for safety)
278
+ execution_results = {
279
+ 'phase': phase,
280
+ 'vpc_count': len(phase_candidates),
281
+ 'dry_run': True, # Force dry-run for safety
282
+ 'execution_plan': [],
283
+ 'warnings': [],
284
+ 'recommendations': []
285
+ }
286
+
287
+ for candidate in phase_candidates:
288
+ vpc_plan = self._generate_vpc_deletion_plan(candidate)
289
+ execution_results['execution_plan'].append(vpc_plan)
290
+
291
+ # Safety warnings
292
+ if candidate.blocking_dependencies > 0:
293
+ execution_results['warnings'].append(
294
+ f"VPC {candidate.vpc_id} has {candidate.blocking_dependencies} blocking dependencies"
295
+ )
296
+
297
+ if candidate.is_default:
298
+ execution_results['warnings'].append(
299
+ f"VPC {candidate.vpc_id} is a default VPC - requires platform approval"
300
+ )
301
+
302
+ # Generate recommendations
303
+ execution_results['recommendations'] = self._generate_execution_recommendations(phase_candidates)
304
+
305
+ print_success(f"VPC cleanup plan generated for {len(phase_candidates)} VPCs")
306
+
307
+ if dry_run:
308
+ print_warning("Dry-run mode: No actual VPC deletions performed")
309
+
310
+ return execution_results
311
+
312
+ def generate_business_report(
313
+ self,
314
+ include_executive_summary: bool = True,
315
+ export_formats: Optional[List[str]] = None
316
+ ) -> Dict[str, Any]:
317
+ """
318
+ Generate business-focused VPC cleanup report
319
+
320
+ Args:
321
+ include_executive_summary: Include executive summary
322
+ export_formats: Export formats (json, csv, html)
323
+
324
+ Returns:
325
+ Dictionary with business report and export information
326
+ """
327
+ print_header("VPC Cleanup Business Report", "Executive Dashboard")
328
+
329
+ if not self.cleanup_framework.cleanup_candidates:
330
+ print_error("No VPC analysis data available. Run analysis first.")
331
+ return {}
332
+
333
+ if not export_formats:
334
+ export_formats = ['json', 'csv']
335
+
336
+ try:
337
+ # Configure manager interface for business reporting
338
+ self.manager_interface.configure_for_business_user(
339
+ safety_mode=self.safety_mode,
340
+ target_savings=30.0, # 30% cost reduction target
341
+ approval_threshold=1000.0 # $1K approval threshold
342
+ )
343
+
344
+ # Convert technical analysis to business insights
345
+ vpc_analysis_results = {
346
+ 'vpc_candidates': self.cleanup_framework.cleanup_candidates,
347
+ 'cleanup_plan': self.cleanup_framework.analysis_results
348
+ }
349
+
350
+ business_analysis = self.manager_interface.analyze_cost_optimization_opportunity(
351
+ vpc_analysis_results
352
+ )
353
+
354
+ # Display business dashboard
355
+ if include_executive_summary:
356
+ self.manager_interface.display_business_dashboard()
357
+
358
+ # Export business reports
359
+ exported_files = self.manager_interface.export_manager_friendly_reports()
360
+
361
+ return {
362
+ 'business_analysis': business_analysis,
363
+ 'recommendations': self.manager_interface.business_recommendations,
364
+ 'executive_presentation': self.manager_interface.generate_executive_presentation(),
365
+ 'exported_files': exported_files
366
+ }
367
+
368
+ except Exception as e:
369
+ print_error(f"Business report generation failed: {e}")
370
+ logger.error(f"Business report error: {e}")
371
+ return {}
372
+
373
+ def validate_vpc_cleanup_safety(
374
+ self,
375
+ vpc_id: str,
376
+ account_profile: Optional[str] = None
377
+ ) -> Dict[str, Any]:
378
+ """
379
+ Validate VPC cleanup safety with comprehensive dependency checking
380
+
381
+ Args:
382
+ vpc_id: VPC ID to validate
383
+ account_profile: AWS profile for the account containing the VPC
384
+
385
+ Returns:
386
+ Dictionary with safety validation results
387
+ """
388
+ print_header(f"VPC Safety Validation", vpc_id)
389
+
390
+ # Find the VPC candidate
391
+ vpc_candidate = None
392
+ for candidate in self.cleanup_framework.cleanup_candidates:
393
+ if candidate.vpc_id == vpc_id:
394
+ vpc_candidate = candidate
395
+ break
396
+
397
+ if not vpc_candidate:
398
+ # Run targeted analysis for this VPC
399
+ profile_to_use = account_profile or self.profile
400
+
401
+ temp_framework = VPCCleanupFramework(
402
+ profile=profile_to_use,
403
+ region=self.region,
404
+ console=self.console,
405
+ safety_mode=True
406
+ )
407
+
408
+ candidates = temp_framework.analyze_vpc_cleanup_candidates(vpc_ids=[vpc_id])
409
+
410
+ if candidates:
411
+ vpc_candidate = candidates[0]
412
+ else:
413
+ print_error(f"VPC {vpc_id} not found or inaccessible")
414
+ return {}
415
+
416
+ # Perform safety validation
417
+ safety_results = {
418
+ 'vpc_id': vpc_id,
419
+ 'safety_score': 'SAFE',
420
+ 'blocking_dependencies': vpc_candidate.blocking_dependencies,
421
+ 'risk_level': vpc_candidate.risk_level.value,
422
+ 'safety_checks': [],
423
+ 'warnings': [],
424
+ 'approval_required': vpc_candidate.approval_required
425
+ }
426
+
427
+ # ENI check (most critical)
428
+ if vpc_candidate.eni_count > 0:
429
+ safety_results['safety_checks'].append({
430
+ 'check': 'ENI Count',
431
+ 'status': 'FAIL',
432
+ 'details': f"{vpc_candidate.eni_count} network interfaces found",
433
+ 'blocking': True
434
+ })
435
+ safety_results['safety_score'] = 'UNSAFE'
436
+ else:
437
+ safety_results['safety_checks'].append({
438
+ 'check': 'ENI Count',
439
+ 'status': 'PASS',
440
+ 'details': 'No active network interfaces',
441
+ 'blocking': False
442
+ })
443
+
444
+ # Dependency checks
445
+ internal_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 1])
446
+ external_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 2])
447
+ control_deps = len([d for d in vpc_candidate.dependencies if d.dependency_level == 3])
448
+
449
+ safety_results['safety_checks'].extend([
450
+ {
451
+ 'check': 'Internal Dependencies',
452
+ 'status': 'WARN' if internal_deps > 0 else 'PASS',
453
+ 'details': f"{internal_deps} internal dependencies (NAT, Endpoints, etc.)",
454
+ 'blocking': internal_deps > 0
455
+ },
456
+ {
457
+ 'check': 'External Dependencies',
458
+ 'status': 'WARN' if external_deps > 0 else 'PASS',
459
+ 'details': f"{external_deps} external dependencies (TGW, Peering, etc.)",
460
+ 'blocking': external_deps > 0
461
+ },
462
+ {
463
+ 'check': 'Control Plane Dependencies',
464
+ 'status': 'WARN' if control_deps > 0 else 'PASS',
465
+ 'details': f"{control_deps} control plane dependencies",
466
+ 'blocking': control_deps > 0
467
+ }
468
+ ])
469
+
470
+ # Update safety score based on blocking dependencies
471
+ blocking_checks = len([c for c in safety_results['safety_checks'] if c['blocking']])
472
+ if blocking_checks > 0:
473
+ safety_results['safety_score'] = 'UNSAFE'
474
+
475
+ # IaC management check
476
+ if vpc_candidate.iac_managed:
477
+ safety_results['warnings'].append(
478
+ f"VPC is managed by Infrastructure as Code: {vpc_candidate.iac_source}"
479
+ )
480
+
481
+ # Default VPC check
482
+ if vpc_candidate.is_default:
483
+ safety_results['warnings'].append(
484
+ "VPC is a default VPC - requires platform team approval"
485
+ )
486
+
487
+ # Display results
488
+ self._display_safety_validation(safety_results)
489
+
490
+ return safety_results
491
+
492
+ def _display_executive_summary(self, cleanup_plan: Dict[str, Any]) -> None:
493
+ """Display executive summary of cleanup plan"""
494
+ if not cleanup_plan:
495
+ return
496
+
497
+ exec_summary = cleanup_plan.get('executive_summary', {})
498
+
499
+ summary_text = (
500
+ f"[bold blue]📊 EXECUTIVE SUMMARY[/bold blue]\n\n"
501
+ f"Total VPCs Analyzed: [yellow]{cleanup_plan['metadata']['total_vpcs_analyzed']}[/yellow]\n"
502
+ f"Ready for Immediate Cleanup: [green]{exec_summary.get('immediate_candidates', 0)}[/green] "
503
+ f"({exec_summary.get('percentage_ready', 0):.1f}%)\n"
504
+ f"Investigation Required: [yellow]{exec_summary.get('investigation_required', 0)}[/yellow]\n"
505
+ f"Governance Approval Needed: [blue]{exec_summary.get('governance_approval_needed', 0)}[/blue]\n"
506
+ f"Complex Migration Required: [red]{exec_summary.get('complex_migration_required', 0)}[/red]\n\n"
507
+ f"Total Annual Savings: [bold green]${cleanup_plan['metadata']['total_annual_savings']:,.2f}[/bold green]\n"
508
+ f"Business Case Strength: [cyan]{exec_summary.get('business_case_strength', 'Unknown')}[/cyan]"
509
+ )
510
+
511
+ self.console.print(Panel(summary_text, title="Executive Summary", style="white", width=80))
512
+
513
+ def _display_execution_plan(self, candidates: List, dry_run: bool) -> None:
514
+ """Display VPC cleanup execution plan"""
515
+ mode_text = "[yellow]DRY RUN MODE[/yellow]" if dry_run else "[red]LIVE EXECUTION MODE[/red]"
516
+
517
+ plan_text = (
518
+ f"[bold blue]🚀 EXECUTION PLAN[/bold blue]\n\n"
519
+ f"Mode: {mode_text}\n"
520
+ f"VPCs to Process: [yellow]{len(candidates)}[/yellow]\n"
521
+ f"Total Dependencies to Remove: [red]{sum(c.blocking_dependencies for c in candidates)}[/red]\n"
522
+ f"High Risk VPCs: [red]{len([c for c in candidates if c.risk_level == VPCCleanupRisk.HIGH])}[/red]\n"
523
+ f"Default VPCs: [magenta]{len([c for c in candidates if c.is_default])}[/magenta]"
524
+ )
525
+
526
+ self.console.print(Panel(plan_text, title="Execution Plan", style="yellow" if dry_run else "red", width=80))
527
+
528
+ def _display_safety_validation(self, safety_results: Dict[str, Any]) -> None:
529
+ """Display VPC safety validation results"""
530
+ # Create safety checks table
531
+ table = create_table(
532
+ title=f"Safety Validation - {safety_results['vpc_id']}",
533
+ columns=[
534
+ {"header": "Check", "style": "cyan"},
535
+ {"header": "Status", "style": "green"},
536
+ {"header": "Details", "style": "white"},
537
+ {"header": "Blocking", "style": "red"}
538
+ ]
539
+ )
540
+
541
+ for check in safety_results['safety_checks']:
542
+ status_color = {
543
+ 'PASS': '[green]✅ PASS[/green]',
544
+ 'WARN': '[yellow]⚠️ WARN[/yellow]',
545
+ 'FAIL': '[red]❌ FAIL[/red]'
546
+ }.get(check['status'], check['status'])
547
+
548
+ blocking_indicator = "🔴 YES" if check['blocking'] else "✅ NO"
549
+
550
+ table.add_row(
551
+ check['check'],
552
+ status_color,
553
+ check['details'],
554
+ blocking_indicator
555
+ )
556
+
557
+ self.console.print(table)
558
+
559
+ # Overall safety assessment
560
+ safety_color = "green" if safety_results['safety_score'] == 'SAFE' else "red"
561
+ assessment_text = (
562
+ f"[bold {safety_color}]Overall Safety: {safety_results['safety_score']}[/bold {safety_color}]\n"
563
+ f"Risk Level: [magenta]{safety_results['risk_level']}[/magenta]\n"
564
+ f"Approval Required: [yellow]{'YES' if safety_results['approval_required'] else 'NO'}[/yellow]"
565
+ )
566
+
567
+ self.console.print(Panel(assessment_text, title="Safety Assessment", style=safety_color, width=60))
568
+
569
+ # Display warnings
570
+ if safety_results['warnings']:
571
+ warnings_text = "\n".join([f"⚠️ {warning}" for warning in safety_results['warnings']])
572
+ self.console.print(Panel(warnings_text, title="Important Warnings", style="yellow", width=80))
573
+
574
+ def _generate_vpc_deletion_plan(self, candidate) -> Dict[str, Any]:
575
+ """Generate detailed VPC deletion plan"""
576
+ deletion_steps = []
577
+
578
+ # Sort dependencies by deletion order
579
+ sorted_deps = sorted(candidate.dependencies, key=lambda x: x.deletion_order)
580
+
581
+ for i, dep in enumerate(sorted_deps, 1):
582
+ deletion_steps.append({
583
+ 'step': i,
584
+ 'action': f"Delete {dep.resource_type}",
585
+ 'resource_id': dep.resource_id,
586
+ 'api_method': dep.api_method,
587
+ 'description': dep.description,
588
+ 'dependency_level': dep.dependency_level
589
+ })
590
+
591
+ # Final VPC deletion step
592
+ deletion_steps.append({
593
+ 'step': len(deletion_steps) + 1,
594
+ 'action': 'Delete VPC',
595
+ 'resource_id': candidate.vpc_id,
596
+ 'api_method': 'delete_vpc',
597
+ 'description': 'Final VPC deletion',
598
+ 'dependency_level': 0
599
+ })
600
+
601
+ return {
602
+ 'vpc_id': candidate.vpc_id,
603
+ 'vpc_name': candidate.vpc_name,
604
+ 'risk_level': candidate.risk_level.value,
605
+ 'total_steps': len(deletion_steps),
606
+ 'estimated_time': f"{len(deletion_steps) * 2} minutes",
607
+ 'deletion_steps': deletion_steps
608
+ }
609
+
610
+ def _generate_execution_recommendations(self, candidates: List) -> List[str]:
611
+ """Generate execution recommendations"""
612
+ recommendations = []
613
+
614
+ # Phase-specific recommendations
615
+ immediate_count = len([c for c in candidates if c.cleanup_phase == VPCCleanupPhase.IMMEDIATE])
616
+ high_risk_count = len([c for c in candidates if c.risk_level == VPCCleanupRisk.HIGH])
617
+ default_vpc_count = len([c for c in candidates if c.is_default])
618
+ iac_managed_count = len([c for c in candidates if c.iac_managed])
619
+
620
+ if immediate_count > 0:
621
+ recommendations.append(
622
+ f"Execute {immediate_count} immediate cleanup candidates first for quick wins"
623
+ )
624
+
625
+ if high_risk_count > 0:
626
+ recommendations.append(
627
+ f"Review {high_risk_count} high-risk VPCs with stakeholders before execution"
628
+ )
629
+
630
+ if default_vpc_count > 0:
631
+ recommendations.append(
632
+ f"Obtain platform team approval for {default_vpc_count} default VPC deletions"
633
+ )
634
+
635
+ if iac_managed_count > 0:
636
+ recommendations.append(
637
+ f"Update Infrastructure as Code for {iac_managed_count} IaC-managed VPCs"
638
+ )
639
+
640
+ # General recommendations
641
+ recommendations.extend([
642
+ "Execute VPC cleanup in phases to minimize blast radius",
643
+ "Validate each deletion step before proceeding to next",
644
+ "Maintain comprehensive audit trail of all deletion activities",
645
+ "Schedule cleanup during maintenance windows to minimize impact"
646
+ ])
647
+
648
+ return recommendations
649
+
650
+
651
+ # CLI Command Functions for integration with runbooks CLI
652
+
653
+ def analyze_cleanup_candidates(
654
+ profile: Optional[str] = None,
655
+ vpc_ids: Optional[List[str]] = None,
656
+ all_accounts: bool = False,
657
+ region: str = "us-east-1",
658
+ export_results: bool = True
659
+ ) -> Dict[str, Any]:
660
+ """
661
+ CLI function to analyze VPC cleanup candidates
662
+
663
+ Args:
664
+ profile: AWS profile for analysis
665
+ vpc_ids: Specific VPC IDs to analyze
666
+ all_accounts: Analyze across all accessible accounts
667
+ region: AWS region
668
+ export_results: Export results to files
669
+
670
+ Returns:
671
+ Dictionary with analysis results
672
+ """
673
+ # Determine profile to use
674
+ operational_profile = get_profile_for_operation("operational", profile)
675
+
676
+ # Initialize CLI wrapper
677
+ cleanup_cli = VPCCleanupCLI(
678
+ profile=operational_profile,
679
+ region=region,
680
+ safety_mode=True # Always enable safety mode
681
+ )
682
+
683
+ # Handle multi-account analysis
684
+ account_profiles = None
685
+ if all_accounts:
686
+ # In a real implementation, this would discover all accessible accounts/profiles
687
+ # For now, we'll use the single profile
688
+ account_profiles = [operational_profile] if operational_profile else None
689
+
690
+ return cleanup_cli.analyze_vpc_cleanup_candidates(
691
+ vpc_ids=vpc_ids,
692
+ account_profiles=account_profiles,
693
+ export_results=export_results
694
+ )
695
+
696
+
697
+ def validate_cleanup_safety(
698
+ vpc_id: str,
699
+ profile: Optional[str] = None,
700
+ region: str = "us-east-1"
701
+ ) -> Dict[str, Any]:
702
+ """
703
+ CLI function to validate VPC cleanup safety
704
+
705
+ Args:
706
+ vpc_id: VPC ID to validate
707
+ profile: AWS profile
708
+ region: AWS region
709
+
710
+ Returns:
711
+ Dictionary with safety validation results
712
+ """
713
+ operational_profile = get_profile_for_operation("operational", profile)
714
+
715
+ cleanup_cli = VPCCleanupCLI(
716
+ profile=operational_profile,
717
+ region=region,
718
+ safety_mode=True
719
+ )
720
+
721
+ return cleanup_cli.validate_vpc_cleanup_safety(
722
+ vpc_id=vpc_id,
723
+ account_profile=operational_profile
724
+ )
725
+
726
+
727
+ def generate_business_report(
728
+ profile: Optional[str] = None,
729
+ region: str = "us-east-1",
730
+ export_formats: Optional[List[str]] = None
731
+ ) -> Dict[str, Any]:
732
+ """
733
+ CLI function to generate business VPC cleanup report
734
+
735
+ Args:
736
+ profile: AWS profile
737
+ region: AWS region
738
+ export_formats: Export formats
739
+
740
+ Returns:
741
+ Dictionary with business report
742
+ """
743
+ operational_profile = get_profile_for_operation("operational", profile)
744
+
745
+ cleanup_cli = VPCCleanupCLI(
746
+ profile=operational_profile,
747
+ region=region,
748
+ safety_mode=True
749
+ )
750
+
751
+ # First run analysis if no candidates exist
752
+ if not cleanup_cli.cleanup_framework.cleanup_candidates:
753
+ cleanup_cli.analyze_vpc_cleanup_candidates()
754
+
755
+ return cleanup_cli.generate_business_report(
756
+ export_formats=export_formats or ['json', 'csv']
757
+ )