runbooks 0.7.6__py3-none-any.whl → 0.7.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/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,679 @@
|
|
1
|
+
"""
|
2
|
+
Rich Formatters - Beautiful console output formatting for VPC operations
|
3
|
+
|
4
|
+
This module provides consistent, beautiful formatting using the Rich library
|
5
|
+
for both CLI and Jupyter notebook environments.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from datetime import datetime
|
9
|
+
from typing import Any, Dict, List, Optional
|
10
|
+
|
11
|
+
from rich import box
|
12
|
+
from rich.console import Console
|
13
|
+
from rich.layout import Layout
|
14
|
+
from rich.panel import Panel
|
15
|
+
from rich.progress import BarColumn, Progress, TextColumn
|
16
|
+
from rich.table import Table
|
17
|
+
from rich.text import Text
|
18
|
+
from rich.tree import Tree
|
19
|
+
|
20
|
+
|
21
|
+
def display_cost_table(console: Console, data: Dict[str, Any], title: str = "Cost Analysis"):
|
22
|
+
"""
|
23
|
+
Display cost data in a beautiful table format
|
24
|
+
|
25
|
+
Args:
|
26
|
+
console: Rich console instance
|
27
|
+
data: Cost data to display
|
28
|
+
title: Table title
|
29
|
+
"""
|
30
|
+
table = Table(title=title, show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
31
|
+
|
32
|
+
# Determine columns based on data
|
33
|
+
if "nat_gateways" in data:
|
34
|
+
table.add_column("Resource", style="cyan")
|
35
|
+
table.add_column("ID", style="yellow")
|
36
|
+
table.add_column("State", style="green")
|
37
|
+
table.add_column("Monthly Cost", justify="right", style="red")
|
38
|
+
table.add_column("Optimization", style="magenta")
|
39
|
+
|
40
|
+
for ng in data["nat_gateways"]:
|
41
|
+
table.add_row(
|
42
|
+
"NAT Gateway",
|
43
|
+
ng["id"][-12:], # Show last 12 chars
|
44
|
+
ng.get("state", "active"),
|
45
|
+
f"${ng.get('monthly_cost', 0):.2f}",
|
46
|
+
ng.get("optimization", {}).get("recommendation", "Optimized"),
|
47
|
+
)
|
48
|
+
|
49
|
+
elif "vpc_endpoints" in data:
|
50
|
+
table.add_column("Endpoint ID", style="cyan")
|
51
|
+
table.add_column("Type", style="yellow")
|
52
|
+
table.add_column("Service", style="green")
|
53
|
+
table.add_column("Monthly Cost", justify="right", style="red")
|
54
|
+
|
55
|
+
for endpoint in data["vpc_endpoints"]:
|
56
|
+
service = endpoint["service"].split(".")[-1] if "." in endpoint["service"] else endpoint["service"]
|
57
|
+
table.add_row(endpoint["id"][-12:], endpoint["type"], service, f"${endpoint.get('monthly_cost', 0):.2f}")
|
58
|
+
|
59
|
+
else:
|
60
|
+
# Generic cost table
|
61
|
+
table.add_column("Item", style="cyan")
|
62
|
+
table.add_column("Value", justify="right", style="yellow")
|
63
|
+
|
64
|
+
for key, value in data.items():
|
65
|
+
if isinstance(value, (int, float)):
|
66
|
+
if "cost" in key.lower():
|
67
|
+
table.add_row(key.replace("_", " ").title(), f"${value:.2f}")
|
68
|
+
else:
|
69
|
+
table.add_row(key.replace("_", " ").title(), str(value))
|
70
|
+
|
71
|
+
console.print(table)
|
72
|
+
|
73
|
+
|
74
|
+
def display_heatmap(console: Console, heat_maps: Dict[str, Any]):
|
75
|
+
"""
|
76
|
+
Display heat map data with visual representation
|
77
|
+
|
78
|
+
Args:
|
79
|
+
console: Rich console instance
|
80
|
+
heat_maps: Heat map data to display
|
81
|
+
"""
|
82
|
+
# Display single account heat map
|
83
|
+
if "single_account_heat_map" in heat_maps:
|
84
|
+
single = heat_maps["single_account_heat_map"]
|
85
|
+
|
86
|
+
# Create a visual heat map using colored blocks
|
87
|
+
console.print(
|
88
|
+
Panel.fit(
|
89
|
+
f"[bold cyan]Single Account Heat Map[/bold cyan]\n"
|
90
|
+
f"Account: {single.get('account_id', 'N/A')}\n"
|
91
|
+
f"Total Monthly Cost: [bold red]${single.get('total_monthly_cost', 0):.2f}[/bold red]",
|
92
|
+
title="🔥 Cost Heat Map",
|
93
|
+
border_style="blue",
|
94
|
+
)
|
95
|
+
)
|
96
|
+
|
97
|
+
# Display regional distribution
|
98
|
+
if "cost_distribution" in single:
|
99
|
+
regional_table = Table(title="Regional Cost Distribution", box=box.SIMPLE)
|
100
|
+
regional_table.add_column("Region", style="cyan")
|
101
|
+
regional_table.add_column("Cost", justify="right", style="yellow")
|
102
|
+
regional_table.add_column("Heat Level", style="red")
|
103
|
+
|
104
|
+
regional_totals = single["cost_distribution"].get("regional_totals", [])
|
105
|
+
regions = single.get("regions", [])
|
106
|
+
|
107
|
+
for idx, (region, cost) in enumerate(zip(regions, regional_totals)):
|
108
|
+
heat_level = _get_heat_level(cost)
|
109
|
+
regional_table.add_row(region, f"${cost:.2f}", heat_level)
|
110
|
+
|
111
|
+
console.print(regional_table)
|
112
|
+
|
113
|
+
# Display multi-account aggregation
|
114
|
+
if "multi_account_aggregated" in heat_maps:
|
115
|
+
multi = heat_maps["multi_account_aggregated"]
|
116
|
+
|
117
|
+
console.print(
|
118
|
+
Panel.fit(
|
119
|
+
f"[bold cyan]Multi-Account Heat Map[/bold cyan]\n"
|
120
|
+
f"Total Accounts: {multi.get('total_accounts', 0)}\n"
|
121
|
+
f"Total Monthly Cost: [bold red]${multi.get('total_monthly_cost', 0):.2f}[/bold red]\n"
|
122
|
+
f"Average per Account: [bold yellow]${multi.get('average_account_cost', 0):.2f}[/bold yellow]",
|
123
|
+
title="🔥 Aggregated Heat Map",
|
124
|
+
border_style="blue",
|
125
|
+
)
|
126
|
+
)
|
127
|
+
|
128
|
+
# Display hotspots
|
129
|
+
if "cost_hotspots" in multi and multi["cost_hotspots"]:
|
130
|
+
hotspot_table = Table(title="Cost Hotspots", box=box.SIMPLE)
|
131
|
+
hotspot_table.add_column("Region", style="cyan")
|
132
|
+
hotspot_table.add_column("Service", style="yellow")
|
133
|
+
hotspot_table.add_column("Cost", justify="right", style="red")
|
134
|
+
hotspot_table.add_column("Severity", style="magenta")
|
135
|
+
|
136
|
+
for hotspot in multi["cost_hotspots"][:10]: # Top 10
|
137
|
+
hotspot_table.add_row(
|
138
|
+
hotspot["region"], hotspot["service"], f"${hotspot['monthly_cost']:.2f}", hotspot["severity"]
|
139
|
+
)
|
140
|
+
|
141
|
+
console.print(hotspot_table)
|
142
|
+
|
143
|
+
# Display time series trends
|
144
|
+
if "time_series_heat_maps" in heat_maps:
|
145
|
+
time_series = heat_maps["time_series_heat_maps"]
|
146
|
+
if "trend_analysis" in time_series:
|
147
|
+
trend = time_series["trend_analysis"]
|
148
|
+
console.print(
|
149
|
+
Panel(
|
150
|
+
f"Growth Rate: [bold yellow]{trend.get('growth_rate', 0)}%[/bold yellow]\n"
|
151
|
+
f"Patterns: {trend.get('seasonal_patterns', 'None')}\n"
|
152
|
+
f"Opportunities: {trend.get('optimization_opportunities', 'None')}",
|
153
|
+
title="📈 Trend Analysis",
|
154
|
+
border_style="green",
|
155
|
+
)
|
156
|
+
)
|
157
|
+
|
158
|
+
|
159
|
+
def display_optimization_recommendations(console: Console, recommendations: Dict[str, Any]):
|
160
|
+
"""
|
161
|
+
Display optimization recommendations with visual impact
|
162
|
+
|
163
|
+
Args:
|
164
|
+
console: Rich console instance
|
165
|
+
recommendations: Optimization recommendations data
|
166
|
+
"""
|
167
|
+
# Display summary panel
|
168
|
+
current = recommendations.get("current_monthly_cost", 0)
|
169
|
+
projected = recommendations.get("projected_monthly_cost", 0)
|
170
|
+
savings = recommendations.get("potential_savings", 0)
|
171
|
+
target = recommendations.get("target_reduction", 0)
|
172
|
+
|
173
|
+
summary_text = f"""
|
174
|
+
[bold cyan]Cost Optimization Summary[/bold cyan]
|
175
|
+
|
176
|
+
Current Monthly Cost: [bold red]${current:.2f}[/bold red]
|
177
|
+
Potential Savings: [bold green]${savings:.2f}[/bold green]
|
178
|
+
Projected Cost: [bold yellow]${projected:.2f}[/bold yellow]
|
179
|
+
Target Reduction: [bold magenta]{target}%[/bold magenta]
|
180
|
+
|
181
|
+
Savings Percentage: [bold green]{(savings / current * 100 if current > 0 else 0):.1f}%[/bold green]
|
182
|
+
Annual Savings: [bold green]${savings * 12:.2f}[/bold green]
|
183
|
+
"""
|
184
|
+
|
185
|
+
console.print(Panel(summary_text.strip(), title="💰 Optimization Summary", border_style="green"))
|
186
|
+
|
187
|
+
# Display recommendations table
|
188
|
+
if "recommendations" in recommendations and recommendations["recommendations"]:
|
189
|
+
rec_table = Table(title="Optimization Recommendations", show_header=True, box=box.ROUNDED)
|
190
|
+
rec_table.add_column("Priority", style="cyan")
|
191
|
+
rec_table.add_column("Resource", style="yellow")
|
192
|
+
rec_table.add_column("Action", style="green")
|
193
|
+
rec_table.add_column("Savings", justify="right", style="red")
|
194
|
+
rec_table.add_column("Risk", style="magenta")
|
195
|
+
rec_table.add_column("Effort", style="blue")
|
196
|
+
|
197
|
+
for idx, rec in enumerate(recommendations["recommendations"][:10], 1):
|
198
|
+
rec_table.add_row(
|
199
|
+
str(idx),
|
200
|
+
rec.get("resource_id", "N/A")[-12:],
|
201
|
+
rec.get("action", "N/A"),
|
202
|
+
f"${rec.get('potential_savings', 0):.2f}",
|
203
|
+
rec.get("risk_level", "medium"),
|
204
|
+
rec.get("implementation_effort", "medium"),
|
205
|
+
)
|
206
|
+
|
207
|
+
console.print(rec_table)
|
208
|
+
|
209
|
+
# Display implementation plan
|
210
|
+
if "implementation_plan" in recommendations and recommendations["implementation_plan"]:
|
211
|
+
plan_tree = Tree("📋 Implementation Plan")
|
212
|
+
|
213
|
+
current_phase = None
|
214
|
+
phase_branch = None
|
215
|
+
|
216
|
+
for item in recommendations["implementation_plan"][:15]: # First 15 items
|
217
|
+
phase = item.get("phase", 1)
|
218
|
+
|
219
|
+
if phase != current_phase:
|
220
|
+
phase_branch = plan_tree.add(f"[bold cyan]Phase {phase}[/bold cyan]")
|
221
|
+
current_phase = phase
|
222
|
+
|
223
|
+
if phase_branch:
|
224
|
+
phase_branch.add(
|
225
|
+
f"{item.get('action', 'N/A')} - "
|
226
|
+
f"[green]${item.get('savings', 0):.2f}[/green] - "
|
227
|
+
f"[yellow]{item.get('risk', 'medium')} risk[/yellow]"
|
228
|
+
)
|
229
|
+
|
230
|
+
console.print(plan_tree)
|
231
|
+
|
232
|
+
|
233
|
+
def display_progress(console: Console, title: str, total: int, current: int):
|
234
|
+
"""
|
235
|
+
Display a progress bar for long-running operations
|
236
|
+
|
237
|
+
Args:
|
238
|
+
console: Rich console instance
|
239
|
+
title: Progress bar title
|
240
|
+
total: Total items
|
241
|
+
current: Current item count
|
242
|
+
"""
|
243
|
+
with Progress(
|
244
|
+
TextColumn("[progress.description]{task.description}"),
|
245
|
+
BarColumn(),
|
246
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
247
|
+
console=console,
|
248
|
+
) as progress:
|
249
|
+
task = progress.add_task(title, total=total)
|
250
|
+
progress.update(task, advance=current)
|
251
|
+
|
252
|
+
|
253
|
+
def display_multi_account_progress(console: Console, accounts: List[str]) -> Progress:
|
254
|
+
"""
|
255
|
+
Create multi-task progress bar for concurrent account analysis
|
256
|
+
|
257
|
+
Args:
|
258
|
+
console: Rich console instance
|
259
|
+
accounts: List of AWS account IDs
|
260
|
+
|
261
|
+
Returns:
|
262
|
+
Progress instance with tasks for each account
|
263
|
+
"""
|
264
|
+
progress = Progress(
|
265
|
+
TextColumn("[progress.description]{task.description}"),
|
266
|
+
BarColumn(bar_width=40),
|
267
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
268
|
+
TextColumn("•"),
|
269
|
+
TextColumn("[blue]{task.completed}/{task.total} accounts"),
|
270
|
+
console=console,
|
271
|
+
)
|
272
|
+
|
273
|
+
# Add tasks for different analysis phases
|
274
|
+
progress.add_task("🔍 Discovery", total=len(accounts))
|
275
|
+
progress.add_task("💰 Cost Analysis", total=len(accounts))
|
276
|
+
progress.add_task("🔥 Heat Maps", total=len(accounts))
|
277
|
+
|
278
|
+
return progress
|
279
|
+
|
280
|
+
|
281
|
+
def display_transit_gateway_architecture(console: Console, tgw_data: Dict[str, Any]):
|
282
|
+
"""
|
283
|
+
Display Transit Gateway architecture as Rich Tree for Issue #97
|
284
|
+
|
285
|
+
Args:
|
286
|
+
console: Rich console instance
|
287
|
+
tgw_data: Transit Gateway architecture data
|
288
|
+
"""
|
289
|
+
tgw_tree = Tree("🌐 [bold cyan]AWS Transit Gateway Architecture[/bold cyan]")
|
290
|
+
|
291
|
+
# Central Egress VPC
|
292
|
+
central_vpc_id = tgw_data.get("central_vpc_id", "vpc-central")
|
293
|
+
central_vpc = tgw_tree.add(f"🏢 [yellow]Central Egress VPC[/yellow] ({central_vpc_id})")
|
294
|
+
|
295
|
+
# Add Transit Gateway details
|
296
|
+
tgw_id = tgw_data.get("transit_gateway_id", "tgw-central")
|
297
|
+
tgw_branch = central_vpc.add(f"🔗 [magenta]Transit Gateway[/magenta] ({tgw_id})")
|
298
|
+
|
299
|
+
# Organizational Units
|
300
|
+
for ou in tgw_data.get("organizational_units", []):
|
301
|
+
ou_branch = tgw_tree.add(f"🏗️ [green]OU: {ou.get('name', 'Unknown')}[/green]")
|
302
|
+
|
303
|
+
# Accounts within OU
|
304
|
+
for account in ou.get("accounts", []):
|
305
|
+
account_id = account.get("id", "unknown")
|
306
|
+
account_branch = ou_branch.add(f"📊 [cyan]Account: {account_id}[/cyan]")
|
307
|
+
|
308
|
+
# VPCs in account
|
309
|
+
for vpc in account.get("vpcs", []):
|
310
|
+
vpc_id = vpc.get("id", "vpc-unknown")
|
311
|
+
monthly_cost = vpc.get("monthly_cost", 0)
|
312
|
+
vpc_branch = account_branch.add(f"🌐 VPC: {vpc_id} - [red]${monthly_cost:.2f}[/red]/month")
|
313
|
+
|
314
|
+
# VPC Endpoints
|
315
|
+
for endpoint in vpc.get("endpoints", []):
|
316
|
+
endpoint_service = endpoint.get("service", "Unknown")
|
317
|
+
endpoint_type = endpoint.get("type", "Interface")
|
318
|
+
vpc_branch.add(f"🔗 {endpoint_service} - [yellow]{endpoint_type}[/yellow]")
|
319
|
+
|
320
|
+
# NAT Gateways
|
321
|
+
for nat in vpc.get("nat_gateways", []):
|
322
|
+
nat_id = nat.get("id", "nat-unknown")
|
323
|
+
nat_cost = nat.get("monthly_cost", 0)
|
324
|
+
vpc_branch.add(f"🚪 NAT Gateway: {nat_id} - [red]${nat_cost:.2f}[/red]/month")
|
325
|
+
|
326
|
+
console.print(tgw_tree)
|
327
|
+
|
328
|
+
|
329
|
+
def display_error(console: Console, error_message: str, suggestion: Optional[str] = None):
|
330
|
+
"""
|
331
|
+
Display an error message with optional suggestion
|
332
|
+
|
333
|
+
Args:
|
334
|
+
console: Rich console instance
|
335
|
+
error_message: Error message to display
|
336
|
+
suggestion: Optional suggestion for resolution
|
337
|
+
"""
|
338
|
+
error_panel = f"[bold red]❌ Error[/bold red]\n\n{error_message}"
|
339
|
+
|
340
|
+
if suggestion:
|
341
|
+
error_panel += f"\n\n[yellow]💡 Suggestion:[/yellow] {suggestion}"
|
342
|
+
|
343
|
+
console.print(Panel(error_panel, border_style="red", box=box.DOUBLE))
|
344
|
+
|
345
|
+
|
346
|
+
def display_success(console: Console, message: str, details: Optional[Dict] = None):
|
347
|
+
"""
|
348
|
+
Display a success message with optional details
|
349
|
+
|
350
|
+
Args:
|
351
|
+
console: Rich console instance
|
352
|
+
message: Success message
|
353
|
+
details: Optional details dictionary
|
354
|
+
"""
|
355
|
+
success_text = f"[bold green]✅ Success[/bold green]\n\n{message}"
|
356
|
+
|
357
|
+
if details:
|
358
|
+
success_text += "\n\n[cyan]Details:[/cyan]\n"
|
359
|
+
for key, value in details.items():
|
360
|
+
success_text += f" • {key}: {value}\n"
|
361
|
+
|
362
|
+
console.print(Panel(success_text, border_style="green"))
|
363
|
+
|
364
|
+
|
365
|
+
def format_currency(value: float) -> str:
|
366
|
+
"""Format a value as currency"""
|
367
|
+
return f"${value:,.2f}"
|
368
|
+
|
369
|
+
|
370
|
+
def format_percentage(value: float) -> str:
|
371
|
+
"""Format a value as percentage"""
|
372
|
+
return f"{value:.1f}%"
|
373
|
+
|
374
|
+
|
375
|
+
def display_optimized_cost_table(console: Console, data: Dict[str, Any], sort_by: str = "monthly_cost"):
|
376
|
+
"""
|
377
|
+
Display cost table with advanced optimization features
|
378
|
+
|
379
|
+
Args:
|
380
|
+
console: Rich console instance
|
381
|
+
data: Cost data with optimization recommendations
|
382
|
+
sort_by: Column to sort by (monthly_cost, potential_savings, risk_level)
|
383
|
+
"""
|
384
|
+
table = Table(
|
385
|
+
title="💰 [bold cyan]Cost Optimization Analysis[/bold cyan]",
|
386
|
+
show_header=True,
|
387
|
+
header_style="bold magenta",
|
388
|
+
box=box.ROUNDED,
|
389
|
+
)
|
390
|
+
|
391
|
+
# Enhanced columns with sorting indicators
|
392
|
+
table.add_column("Resource", style="cyan")
|
393
|
+
table.add_column("ID", style="yellow")
|
394
|
+
table.add_column("Monthly Cost ⬇️", justify="right", style="red")
|
395
|
+
table.add_column("Potential Savings", justify="right", style="green")
|
396
|
+
table.add_column("Optimization Priority", style="magenta")
|
397
|
+
table.add_column("Risk Level", style="blue")
|
398
|
+
|
399
|
+
# Sort data by specified column
|
400
|
+
if "nat_gateways" in data:
|
401
|
+
sorted_items = sorted(data["nat_gateways"], key=lambda x: x.get(sort_by, 0), reverse=True)
|
402
|
+
|
403
|
+
for ng in sorted_items:
|
404
|
+
# Color-coded priority indicators
|
405
|
+
priority = _get_optimization_priority(ng)
|
406
|
+
risk_indicator = _get_risk_indicator(ng.get("optimization", {}).get("risk_level", "medium"))
|
407
|
+
|
408
|
+
table.add_row(
|
409
|
+
"NAT Gateway",
|
410
|
+
ng["id"][-12:],
|
411
|
+
f"${ng.get('monthly_cost', 0):.2f}",
|
412
|
+
f"${ng.get('optimization', {}).get('potential_savings', 0):.2f}",
|
413
|
+
priority,
|
414
|
+
risk_indicator,
|
415
|
+
)
|
416
|
+
|
417
|
+
elif "vpc_endpoints" in data:
|
418
|
+
sorted_items = sorted(data["vpc_endpoints"], key=lambda x: x.get(sort_by, 0), reverse=True)
|
419
|
+
|
420
|
+
for endpoint in sorted_items:
|
421
|
+
priority = _get_optimization_priority(endpoint)
|
422
|
+
risk_indicator = _get_risk_indicator(endpoint.get("optimization", {}).get("risk_level", "low"))
|
423
|
+
|
424
|
+
# Shorten service name for display
|
425
|
+
service = (
|
426
|
+
endpoint["service"].split(".")[-1]
|
427
|
+
if "." in endpoint.get("service", "")
|
428
|
+
else endpoint.get("service", "Unknown")
|
429
|
+
)
|
430
|
+
|
431
|
+
table.add_row(
|
432
|
+
f"VPC Endpoint ({endpoint.get('type', 'Interface')})",
|
433
|
+
endpoint["id"][-12:],
|
434
|
+
f"${endpoint.get('monthly_cost', 0):.2f}",
|
435
|
+
f"${endpoint.get('optimization', {}).get('potential_savings', 0):.2f}",
|
436
|
+
priority,
|
437
|
+
risk_indicator,
|
438
|
+
)
|
439
|
+
|
440
|
+
console.print(table)
|
441
|
+
|
442
|
+
|
443
|
+
def _get_optimization_priority(resource: Dict) -> str:
|
444
|
+
"""Get color-coded optimization priority"""
|
445
|
+
savings = resource.get("optimization", {}).get("potential_savings", 0)
|
446
|
+
if savings > 40:
|
447
|
+
return "[bold red]🔥 CRITICAL[/bold red]"
|
448
|
+
elif savings > 20:
|
449
|
+
return "[red]⚠️ HIGH[/red]"
|
450
|
+
elif savings > 10:
|
451
|
+
return "[yellow]📈 MEDIUM[/yellow]"
|
452
|
+
else:
|
453
|
+
return "[green]✅ LOW[/green]"
|
454
|
+
|
455
|
+
|
456
|
+
def _get_risk_indicator(risk_level: str) -> str:
|
457
|
+
"""Get color-coded risk indicator"""
|
458
|
+
risk_colors = {
|
459
|
+
"high": "[bold red]🔴 HIGH[/bold red]",
|
460
|
+
"medium": "[yellow]🟡 MEDIUM[/yellow]",
|
461
|
+
"low": "[green]🟢 LOW[/green]",
|
462
|
+
}
|
463
|
+
return risk_colors.get(risk_level.lower(), "[yellow]🟡 MEDIUM[/yellow]")
|
464
|
+
|
465
|
+
|
466
|
+
def _get_heat_level(cost: float) -> str:
|
467
|
+
"""
|
468
|
+
Get heat level indicator based on cost
|
469
|
+
|
470
|
+
Args:
|
471
|
+
cost: Cost value
|
472
|
+
|
473
|
+
Returns:
|
474
|
+
Heat level string with color
|
475
|
+
"""
|
476
|
+
if cost > 500:
|
477
|
+
return "[bold red]🔥🔥🔥 CRITICAL[/bold red]"
|
478
|
+
elif cost > 100:
|
479
|
+
return "[red]🔥🔥 HIGH[/red]"
|
480
|
+
elif cost > 50:
|
481
|
+
return "[yellow]🔥 MEDIUM[/yellow]"
|
482
|
+
elif cost > 10:
|
483
|
+
return "[green]• LOW[/green]"
|
484
|
+
else:
|
485
|
+
return "[dim]○ MINIMAL[/dim]"
|
486
|
+
|
487
|
+
|
488
|
+
def create_summary_layout(data: Dict[str, Any]) -> Layout:
|
489
|
+
"""
|
490
|
+
Create a summary layout for comprehensive display
|
491
|
+
|
492
|
+
Args:
|
493
|
+
data: Summary data to display
|
494
|
+
|
495
|
+
Returns:
|
496
|
+
Rich Layout object
|
497
|
+
"""
|
498
|
+
layout = Layout()
|
499
|
+
|
500
|
+
# Create main sections
|
501
|
+
layout.split_column(Layout(name="header", size=3), Layout(name="body"), Layout(name="footer", size=3))
|
502
|
+
|
503
|
+
# Header
|
504
|
+
layout["header"].update(Panel("[bold cyan]VPC Networking Cost Analysis[/bold cyan]", style="blue"))
|
505
|
+
|
506
|
+
# Body split into columns
|
507
|
+
layout["body"].split_row(Layout(name="costs"), Layout(name="recommendations"))
|
508
|
+
|
509
|
+
# Footer
|
510
|
+
layout["footer"].update(Panel(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", style="dim"))
|
511
|
+
|
512
|
+
return layout
|
513
|
+
|
514
|
+
|
515
|
+
def display_transit_gateway_analysis(results: Dict[str, Any], console: Console) -> None:
|
516
|
+
"""
|
517
|
+
Display comprehensive Transit Gateway analysis results with Rich formatting.
|
518
|
+
|
519
|
+
Enhanced for Issue #97: Strategic executive reporting with $325+/month savings focus
|
520
|
+
and business impact visualization for 60-account enterprise environment.
|
521
|
+
"""
|
522
|
+
|
523
|
+
# Executive header with business impact
|
524
|
+
console.print("\n🎯 Enterprise Transit Gateway Strategic Analysis", style="bold bright_blue")
|
525
|
+
console.print("Issue #97: Multi-Account Cost Optimization Campaign", style="dim italic")
|
526
|
+
console.print("=" * 85, style="dim")
|
527
|
+
|
528
|
+
# Executive Business Impact Panel (TOP PRIORITY)
|
529
|
+
cost_analysis = results.get("cost_analysis", {})
|
530
|
+
business_impact = cost_analysis.get("business_impact", {})
|
531
|
+
|
532
|
+
if business_impact:
|
533
|
+
executive_panel = Panel(
|
534
|
+
f"💰 Monthly Savings: [bold green]${business_impact.get('monthly_savings', 0):.0f}[/bold green]\n"
|
535
|
+
f"📊 Annual Impact: [bold cyan]${business_impact.get('annual_savings', 0):,.0f}[/bold cyan]\n"
|
536
|
+
f"🎯 Target Achievement: [bold yellow]{business_impact.get('target_achievement', 'N/A')}[/bold yellow]\n"
|
537
|
+
f"⭐ ROI Grade: [bold magenta]{business_impact.get('roi_grade', 'UNKNOWN')}[/bold magenta]\n"
|
538
|
+
f"📋 {business_impact.get('executive_summary', 'Analysis pending')}",
|
539
|
+
title="🎯 Executive Business Impact",
|
540
|
+
border_style="green" if business_impact.get('roi_grade') == 'EXCEEDS TARGET' else "yellow"
|
541
|
+
)
|
542
|
+
console.print(executive_panel)
|
543
|
+
|
544
|
+
# Enhanced summary metrics table with optimization focus
|
545
|
+
summary_table = Table(title="📊 Strategic Analysis Dashboard", show_header=True, header_style="bold magenta")
|
546
|
+
summary_table.add_column("Metric", style="cyan", no_wrap=True)
|
547
|
+
summary_table.add_column("Current State", style="green")
|
548
|
+
summary_table.add_column("Optimization Target", style="yellow")
|
549
|
+
summary_table.add_column("Business Value", style="bold green")
|
550
|
+
|
551
|
+
tgw_count = len(results.get("transit_gateways", []))
|
552
|
+
attachment_count = len(results.get("attachments", []))
|
553
|
+
route_table_count = len(results.get("route_tables", []))
|
554
|
+
|
555
|
+
total_cost = cost_analysis.get("total_monthly_cost", 0)
|
556
|
+
savings_potential = cost_analysis.get("savings_potential", 0)
|
557
|
+
|
558
|
+
summary_table.add_row("Transit Gateways", str(tgw_count), "Optimized topology", f"Architecture review")
|
559
|
+
summary_table.add_row("Attachments", str(attachment_count), f"{attachment_count * 0.85:.0f} (15% reduction)", "Remove underutilized")
|
560
|
+
summary_table.add_row("Route Tables", str(route_table_count), f"{route_table_count * 0.75:.0f} (25% consolidation)", "Streamlined routing")
|
561
|
+
summary_table.add_row("Monthly Cost", f"${total_cost:.2f}", f"${total_cost - savings_potential:.2f}", f"-${savings_potential:.0f}/month")
|
562
|
+
summary_table.add_row("Annual Savings", "Current baseline", f"${savings_potential * 12:,.0f}/year", "Target: $325+/month")
|
563
|
+
|
564
|
+
console.print(summary_table)
|
565
|
+
|
566
|
+
# Transit Gateway details
|
567
|
+
if results.get("transit_gateways"):
|
568
|
+
tgw_table = Table(title="🌉 Transit Gateway Details", show_header=True, header_style="bold blue")
|
569
|
+
tgw_table.add_column("ID", style="cyan")
|
570
|
+
tgw_table.add_column("State", style="green")
|
571
|
+
tgw_table.add_column("Owner", style="yellow")
|
572
|
+
tgw_table.add_column("ASN", style="magenta")
|
573
|
+
tgw_table.add_column("Description", style="white")
|
574
|
+
|
575
|
+
for tgw in results["transit_gateways"]:
|
576
|
+
tgw_table.add_row(
|
577
|
+
tgw.get("TransitGatewayId", "")[:20],
|
578
|
+
tgw.get("State", ""),
|
579
|
+
tgw.get("OwnerId", "")[:12],
|
580
|
+
str(tgw.get("AmazonSideAsn", "")),
|
581
|
+
tgw.get("Description", "No description")[:30],
|
582
|
+
)
|
583
|
+
|
584
|
+
console.print(tgw_table)
|
585
|
+
|
586
|
+
# Central Egress VPC information
|
587
|
+
if results.get("central_egress_vpc"):
|
588
|
+
egress_vpc = results["central_egress_vpc"]
|
589
|
+
egress_panel = Panel(
|
590
|
+
f"[bold green]VPC ID:[/bold green] {egress_vpc.get('VpcId', 'N/A')}\n"
|
591
|
+
f"[bold green]Name:[/bold green] {egress_vpc.get('VpcName', 'N/A')}\n"
|
592
|
+
f"[bold green]CIDR:[/bold green] {egress_vpc.get('CidrBlock', 'N/A')}\n"
|
593
|
+
f"[bold green]Transit Gateway:[/bold green] {egress_vpc.get('TransitGatewayId', 'N/A')}",
|
594
|
+
title="🏗️ Central Egress VPC",
|
595
|
+
border_style="green",
|
596
|
+
)
|
597
|
+
console.print(egress_panel)
|
598
|
+
|
599
|
+
# Cost breakdown
|
600
|
+
cost_analysis = results.get("cost_analysis", {})
|
601
|
+
if cost_analysis.get("cost_breakdown"):
|
602
|
+
cost_table = Table(title="💰 Cost Breakdown", show_header=True, header_style="bold green")
|
603
|
+
cost_table.add_column("Component", style="cyan")
|
604
|
+
cost_table.add_column("Monthly Cost", style="green", justify="right")
|
605
|
+
cost_table.add_column("Percentage", style="yellow", justify="right")
|
606
|
+
|
607
|
+
total_cost = cost_analysis.get("total_monthly_cost", 0)
|
608
|
+
for component in cost_analysis["cost_breakdown"]:
|
609
|
+
cost = component.get("monthly_cost", 0)
|
610
|
+
percentage = (cost / total_cost * 100) if total_cost > 0 else 0
|
611
|
+
cost_table.add_row(component.get("component", ""), f"${cost:.2f}", f"{percentage:.1f}%")
|
612
|
+
|
613
|
+
console.print(cost_table)
|
614
|
+
|
615
|
+
# Optimization recommendations
|
616
|
+
recommendations = results.get("optimization_recommendations", [])
|
617
|
+
if recommendations:
|
618
|
+
rec_table = Table(title="🎯 Optimization Recommendations", show_header=True, header_style="bold yellow")
|
619
|
+
rec_table.add_column("Priority", style="red")
|
620
|
+
rec_table.add_column("Title", style="cyan")
|
621
|
+
rec_table.add_column("Monthly Savings", style="green", justify="right")
|
622
|
+
rec_table.add_column("Effort", style="yellow")
|
623
|
+
rec_table.add_column("Description", style="white")
|
624
|
+
|
625
|
+
for rec in recommendations[:5]: # Top 5 recommendations
|
626
|
+
rec_table.add_row(
|
627
|
+
rec.get("priority", ""),
|
628
|
+
rec.get("title", ""),
|
629
|
+
f"${rec.get('monthly_savings', 0):.2f}",
|
630
|
+
rec.get("effort", ""),
|
631
|
+
rec.get("description", "")[:50] + "..."
|
632
|
+
if len(rec.get("description", "")) > 50
|
633
|
+
else rec.get("description", ""),
|
634
|
+
)
|
635
|
+
|
636
|
+
console.print(rec_table)
|
637
|
+
|
638
|
+
# Architecture gaps and drift detection
|
639
|
+
gaps = results.get("architecture_gaps", [])
|
640
|
+
if gaps:
|
641
|
+
gap_table = Table(title="⚠️ Architecture Gaps & Drift Detection", show_header=True, header_style="bold red")
|
642
|
+
gap_table.add_column("Category", style="cyan")
|
643
|
+
gap_table.add_column("Severity", style="red")
|
644
|
+
gap_table.add_column("Description", style="white")
|
645
|
+
gap_table.add_column("Details", style="yellow")
|
646
|
+
|
647
|
+
for gap in gaps:
|
648
|
+
severity_style = {
|
649
|
+
"Info": "blue",
|
650
|
+
"Warning": "yellow",
|
651
|
+
"Medium": "orange",
|
652
|
+
"High": "red",
|
653
|
+
"Critical": "bright_red",
|
654
|
+
}.get(gap.get("severity", ""), "white")
|
655
|
+
|
656
|
+
gap_table.add_row(
|
657
|
+
gap.get("category", ""),
|
658
|
+
f"[{severity_style}]{gap.get('severity', '')}[/{severity_style}]",
|
659
|
+
gap.get("description", ""),
|
660
|
+
gap.get("details", "")[:60],
|
661
|
+
)
|
662
|
+
|
663
|
+
console.print(gap_table)
|
664
|
+
|
665
|
+
# Footer with next steps
|
666
|
+
next_steps = Panel(
|
667
|
+
"[bold cyan]Next Steps:[/bold cyan]\n"
|
668
|
+
"1. Review optimization recommendations by priority\n"
|
669
|
+
"2. Address architecture gaps and drift detection issues\n"
|
670
|
+
"3. Implement centralized VPC endpoint sharing\n"
|
671
|
+
"4. Monitor cost savings and performance impact\n"
|
672
|
+
"5. Schedule regular analysis runs for continuous optimization",
|
673
|
+
title="🚀 Recommended Actions",
|
674
|
+
border_style="bright_blue",
|
675
|
+
)
|
676
|
+
console.print(next_steps)
|
677
|
+
|
678
|
+
# Analysis timestamp
|
679
|
+
console.print(f"\n[dim]Analysis completed: {results.get('analysis_timestamp', 'N/A')}[/dim]")
|