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.
- runbooks/__init__.py +1 -1
- runbooks/common/mcp_integration.py +174 -0
- runbooks/common/performance_monitor.py +4 -4
- runbooks/common/rich_utils.py +3 -0
- runbooks/enterprise/__init__.py +18 -10
- runbooks/enterprise/security.py +708 -0
- runbooks/finops/enhanced_dashboard_runner.py +2 -1
- runbooks/finops/finops_dashboard.py +322 -11
- runbooks/finops/markdown_exporter.py +226 -0
- runbooks/finops/optimizer.py +2 -0
- runbooks/finops/single_dashboard.py +16 -16
- runbooks/finops/vpc_cleanup_exporter.py +328 -0
- runbooks/finops/vpc_cleanup_optimizer.py +1318 -0
- runbooks/main.py +384 -15
- runbooks/operate/vpc_operations.py +8 -2
- runbooks/vpc/__init__.py +12 -0
- runbooks/vpc/cleanup_wrapper.py +757 -0
- runbooks/vpc/cost_engine.py +527 -3
- runbooks/vpc/networking_wrapper.py +29 -29
- runbooks/vpc/runbooks_adapter.py +479 -0
- runbooks/vpc/unified_scenarios.py +3199 -0
- runbooks/vpc/vpc_cleanup_integration.py +2629 -0
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/METADATA +1 -1
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/RECORD +28 -21
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/WHEEL +0 -0
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.7.dist-info → runbooks-0.9.9.dist-info}/top_level.txt +0 -0
@@ -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
|
+
)
|