runbooks 0.9.6__py3-none-any.whl → 0.9.8__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/_platform/__init__.py +19 -0
- runbooks/_platform/core/runbooks_wrapper.py +478 -0
- runbooks/cloudops/cost_optimizer.py +330 -0
- runbooks/cloudops/interfaces.py +3 -3
- runbooks/common/mcp_integration.py +174 -0
- runbooks/common/performance_monitor.py +4 -4
- runbooks/enterprise/__init__.py +18 -10
- runbooks/enterprise/security.py +708 -0
- runbooks/finops/README.md +1 -1
- runbooks/finops/automation_core.py +643 -0
- runbooks/finops/business_cases.py +414 -16
- runbooks/finops/cli.py +23 -0
- runbooks/finops/compute_cost_optimizer.py +865 -0
- runbooks/finops/ebs_cost_optimizer.py +718 -0
- runbooks/finops/ebs_optimizer.py +909 -0
- runbooks/finops/elastic_ip_optimizer.py +675 -0
- runbooks/finops/embedded_mcp_validator.py +330 -14
- runbooks/finops/enhanced_dashboard_runner.py +2 -1
- runbooks/finops/enterprise_wrappers.py +827 -0
- runbooks/finops/finops_dashboard.py +322 -11
- runbooks/finops/legacy_migration.py +730 -0
- runbooks/finops/nat_gateway_optimizer.py +1160 -0
- runbooks/finops/network_cost_optimizer.py +1387 -0
- runbooks/finops/notebook_utils.py +596 -0
- runbooks/finops/reservation_optimizer.py +956 -0
- runbooks/finops/single_dashboard.py +16 -16
- runbooks/finops/validation_framework.py +753 -0
- runbooks/finops/vpc_cleanup_optimizer.py +817 -0
- runbooks/finops/workspaces_analyzer.py +1 -1
- runbooks/inventory/__init__.py +7 -0
- runbooks/inventory/collectors/aws_networking.py +357 -6
- runbooks/inventory/mcp_vpc_validator.py +1091 -0
- runbooks/inventory/vpc_analyzer.py +1107 -0
- runbooks/inventory/vpc_architecture_validator.py +939 -0
- runbooks/inventory/vpc_dependency_analyzer.py +845 -0
- runbooks/main.py +487 -40
- runbooks/operate/vpc_operations.py +1485 -16
- runbooks/remediation/commvault_ec2_analysis.py +1 -1
- runbooks/remediation/dynamodb_optimize.py +2 -2
- runbooks/remediation/rds_instance_list.py +1 -1
- runbooks/remediation/rds_snapshot_list.py +1 -1
- runbooks/remediation/workspaces_list.py +2 -2
- runbooks/security/compliance_automation.py +2 -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/tests/test_config.py +2 -2
- runbooks/vpc/vpc_cleanup_integration.py +2629 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/METADATA +1 -1
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/RECORD +57 -34
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/WHEEL +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/top_level.txt +0 -0
runbooks/__init__.py
CHANGED
@@ -61,7 +61,7 @@ s3_ops = S3Operations()
|
|
61
61
|
|
62
62
|
# Centralized Version Management - Single Source of Truth
|
63
63
|
# All modules MUST import __version__ from this location
|
64
|
-
__version__ = "0.9.
|
64
|
+
__version__ = "0.9.7"
|
65
65
|
|
66
66
|
# Fallback for legacy importlib.metadata usage during transition
|
67
67
|
try:
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
Enterprise FinOps Platform Integration Layer
|
3
|
+
|
4
|
+
This module provides the integration layer between the runbooks package
|
5
|
+
and the FinOps notebook interfaces, enabling business-friendly access
|
6
|
+
to technical cost optimization capabilities.
|
7
|
+
"""
|
8
|
+
|
9
|
+
__version__ = "1.0.0"
|
10
|
+
|
11
|
+
from .core.runbooks_wrapper import RunbooksWrapper
|
12
|
+
from .finops.unit_economics import UnitEconomicsCalculator
|
13
|
+
from .components.validators import ProfileValidator
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"RunbooksWrapper",
|
17
|
+
"UnitEconomicsCalculator",
|
18
|
+
"ProfileValidator"
|
19
|
+
]
|
@@ -0,0 +1,478 @@
|
|
1
|
+
"""
|
2
|
+
Enterprise FinOps Platform - RunbooksWrapper
|
3
|
+
Provides unified access to all runbooks CLI commands with MCP validation and Rich output
|
4
|
+
"""
|
5
|
+
import subprocess
|
6
|
+
import json
|
7
|
+
import yaml
|
8
|
+
from typing import Dict, List, Optional, Any, Union
|
9
|
+
from pathlib import Path
|
10
|
+
from datetime import datetime
|
11
|
+
import pandas as pd
|
12
|
+
|
13
|
+
from rich.console import Console
|
14
|
+
from rich.table import Table
|
15
|
+
from rich.panel import Panel
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
17
|
+
from rich import box
|
18
|
+
|
19
|
+
console = Console()
|
20
|
+
|
21
|
+
class RunbooksWrapper:
|
22
|
+
"""
|
23
|
+
Enterprise wrapper for runbooks CLI commands with MCP validation and Rich output.
|
24
|
+
|
25
|
+
Provides Jupyter-friendly interface to all runbooks functionality:
|
26
|
+
- Inventory collection and analysis
|
27
|
+
- FinOps cost analysis and optimization
|
28
|
+
- Security assessments and remediation
|
29
|
+
- CFAT well-architected evaluations
|
30
|
+
- Operations automation
|
31
|
+
- Organization management
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, default_profile: Optional[str] = None):
|
35
|
+
"""Initialize wrapper with optional default AWS profile."""
|
36
|
+
self.default_profile = default_profile
|
37
|
+
self.console = Console()
|
38
|
+
|
39
|
+
def _execute_command(self, command: str, capture_output: bool = True) -> Dict[str, Any]:
|
40
|
+
"""
|
41
|
+
Execute runbooks command with error handling and rich output.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
command: Full runbooks command to execute
|
45
|
+
capture_output: Whether to capture command output
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
Dictionary with command result, output, and metadata
|
49
|
+
"""
|
50
|
+
try:
|
51
|
+
with Progress(
|
52
|
+
SpinnerColumn(),
|
53
|
+
TextColumn("[progress.description]{task.description}"),
|
54
|
+
console=console,
|
55
|
+
transient=True,
|
56
|
+
) as progress:
|
57
|
+
progress.add_task(description="Executing command...", total=None)
|
58
|
+
|
59
|
+
result = subprocess.run(
|
60
|
+
command.split(),
|
61
|
+
capture_output=capture_output,
|
62
|
+
text=True,
|
63
|
+
timeout=300 # 5 minute timeout
|
64
|
+
)
|
65
|
+
|
66
|
+
return {
|
67
|
+
"success": result.returncode == 0,
|
68
|
+
"returncode": result.returncode,
|
69
|
+
"stdout": result.stdout,
|
70
|
+
"stderr": result.stderr,
|
71
|
+
"command": command,
|
72
|
+
"timestamp": datetime.now().isoformat()
|
73
|
+
}
|
74
|
+
|
75
|
+
except subprocess.TimeoutExpired:
|
76
|
+
return {
|
77
|
+
"success": False,
|
78
|
+
"error": "Command timed out after 5 minutes",
|
79
|
+
"command": command,
|
80
|
+
"timestamp": datetime.now().isoformat()
|
81
|
+
}
|
82
|
+
except Exception as e:
|
83
|
+
return {
|
84
|
+
"success": False,
|
85
|
+
"error": str(e),
|
86
|
+
"command": command,
|
87
|
+
"timestamp": datetime.now().isoformat()
|
88
|
+
}
|
89
|
+
|
90
|
+
# Inventory Operations
|
91
|
+
def inventory_collect(self,
|
92
|
+
resources: List[str] = None,
|
93
|
+
profile: str = None,
|
94
|
+
all_accounts: bool = False,
|
95
|
+
include_costs: bool = False,
|
96
|
+
regions: List[str] = None) -> pd.DataFrame:
|
97
|
+
"""
|
98
|
+
Collect inventory across AWS accounts and services.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
resources: List of AWS resources (ec2, s3, rds, lambda, etc.)
|
102
|
+
profile: AWS profile to use
|
103
|
+
all_accounts: Scan all accounts in organization
|
104
|
+
include_costs: Include cost analysis
|
105
|
+
regions: Specific regions to scan
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
DataFrame with inventory results
|
109
|
+
"""
|
110
|
+
cmd_parts = ["runbooks", "inventory", "collect"]
|
111
|
+
|
112
|
+
if resources:
|
113
|
+
cmd_parts.extend(["-r", ",".join(resources)])
|
114
|
+
if profile or self.default_profile:
|
115
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
116
|
+
if all_accounts:
|
117
|
+
cmd_parts.append("--all-accounts")
|
118
|
+
if include_costs:
|
119
|
+
cmd_parts.append("--include-costs")
|
120
|
+
if regions:
|
121
|
+
cmd_parts.extend(["--regions", ",".join(regions)])
|
122
|
+
|
123
|
+
command = " ".join(cmd_parts)
|
124
|
+
result = self._execute_command(command)
|
125
|
+
|
126
|
+
if result["success"]:
|
127
|
+
try:
|
128
|
+
# Parse JSON output to DataFrame
|
129
|
+
data = json.loads(result["stdout"])
|
130
|
+
df = pd.DataFrame(data.get("resources", []))
|
131
|
+
self._display_inventory_summary(df)
|
132
|
+
return df
|
133
|
+
except json.JSONDecodeError:
|
134
|
+
console.print("[yellow]Warning: Could not parse JSON output, returning raw text[/yellow]")
|
135
|
+
return pd.DataFrame({"output": [result["stdout"]]})
|
136
|
+
else:
|
137
|
+
console.print(f"[red]Error executing inventory command: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
138
|
+
return pd.DataFrame()
|
139
|
+
|
140
|
+
# FinOps Operations
|
141
|
+
def finops_analyze(self,
|
142
|
+
profile: str = None,
|
143
|
+
all_accounts: bool = False,
|
144
|
+
target_reduction: str = "20-40%",
|
145
|
+
breakdown_by: List[str] = None,
|
146
|
+
export_format: str = "json") -> Dict[str, Any]:
|
147
|
+
"""
|
148
|
+
Perform FinOps cost analysis and optimization recommendations.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
profile: AWS billing profile
|
152
|
+
all_accounts: Analyze all accounts
|
153
|
+
target_reduction: Target cost reduction percentage
|
154
|
+
breakdown_by: Breakdown by service, account, region
|
155
|
+
export_format: Export format (json, csv, html)
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
Dictionary with cost analysis results
|
159
|
+
"""
|
160
|
+
cmd_parts = ["runbooks", "finops", "--analyze"]
|
161
|
+
|
162
|
+
if profile or self.default_profile:
|
163
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
164
|
+
if all_accounts:
|
165
|
+
cmd_parts.append("--all-accounts")
|
166
|
+
if target_reduction:
|
167
|
+
cmd_parts.extend(["--target-reduction", target_reduction])
|
168
|
+
if breakdown_by:
|
169
|
+
cmd_parts.extend(["--breakdown-by", ",".join(breakdown_by)])
|
170
|
+
if export_format:
|
171
|
+
cmd_parts.extend(["--export", export_format])
|
172
|
+
|
173
|
+
command = " ".join(cmd_parts)
|
174
|
+
result = self._execute_command(command)
|
175
|
+
|
176
|
+
if result["success"]:
|
177
|
+
try:
|
178
|
+
data = json.loads(result["stdout"])
|
179
|
+
self._display_finops_summary(data)
|
180
|
+
return data
|
181
|
+
except json.JSONDecodeError:
|
182
|
+
console.print("[yellow]Warning: Could not parse JSON output[/yellow]")
|
183
|
+
return {"raw_output": result["stdout"]}
|
184
|
+
else:
|
185
|
+
console.print(f"[red]Error executing FinOps analysis: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
186
|
+
return {}
|
187
|
+
|
188
|
+
# Security Operations
|
189
|
+
def security_assess(self,
|
190
|
+
profile: str = None,
|
191
|
+
all_accounts: bool = False,
|
192
|
+
checks: str = "all",
|
193
|
+
language: str = "EN",
|
194
|
+
format: str = "json") -> Dict[str, Any]:
|
195
|
+
"""
|
196
|
+
Perform security assessment across accounts.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
profile: AWS profile to use
|
200
|
+
all_accounts: Assess all accounts
|
201
|
+
checks: Specific security checks or "all"
|
202
|
+
language: Report language (EN, JP, KR, VN)
|
203
|
+
format: Output format (json, html, csv)
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Dictionary with security assessment results
|
207
|
+
"""
|
208
|
+
cmd_parts = ["runbooks", "security", "assess"]
|
209
|
+
|
210
|
+
if profile or self.default_profile:
|
211
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
212
|
+
if all_accounts:
|
213
|
+
cmd_parts.append("--all-accounts")
|
214
|
+
if checks:
|
215
|
+
cmd_parts.extend(["--checks", checks])
|
216
|
+
if language:
|
217
|
+
cmd_parts.extend(["--language", language])
|
218
|
+
if format:
|
219
|
+
cmd_parts.extend(["--format", format])
|
220
|
+
|
221
|
+
command = " ".join(cmd_parts)
|
222
|
+
result = self._execute_command(command)
|
223
|
+
|
224
|
+
if result["success"]:
|
225
|
+
try:
|
226
|
+
data = json.loads(result["stdout"])
|
227
|
+
self._display_security_summary(data)
|
228
|
+
return data
|
229
|
+
except json.JSONDecodeError:
|
230
|
+
return {"raw_output": result["stdout"]}
|
231
|
+
else:
|
232
|
+
console.print(f"[red]Error executing security assessment: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
233
|
+
return {}
|
234
|
+
|
235
|
+
# CFAT Operations
|
236
|
+
def cfat_assess(self,
|
237
|
+
profile: str = None,
|
238
|
+
compliance_framework: str = "AWS Well-Architected",
|
239
|
+
output_format: str = "json",
|
240
|
+
serve_web: bool = False,
|
241
|
+
port: int = 8080) -> Dict[str, Any]:
|
242
|
+
"""
|
243
|
+
Perform Cloud Foundation Assessment Tool evaluation.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
profile: AWS profile to use
|
247
|
+
compliance_framework: Framework to assess against
|
248
|
+
output_format: Output format
|
249
|
+
serve_web: Start web server for results
|
250
|
+
port: Web server port
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
Dictionary with CFAT assessment results
|
254
|
+
"""
|
255
|
+
cmd_parts = ["runbooks", "cfat", "assess"]
|
256
|
+
|
257
|
+
if profile or self.default_profile:
|
258
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
259
|
+
if compliance_framework:
|
260
|
+
cmd_parts.extend(["--compliance-framework", f'"{compliance_framework}"'])
|
261
|
+
if output_format:
|
262
|
+
cmd_parts.extend(["--output", output_format])
|
263
|
+
if serve_web:
|
264
|
+
cmd_parts.extend(["--serve-web", "--port", str(port)])
|
265
|
+
|
266
|
+
command = " ".join(cmd_parts)
|
267
|
+
result = self._execute_command(command)
|
268
|
+
|
269
|
+
if result["success"]:
|
270
|
+
try:
|
271
|
+
data = json.loads(result["stdout"])
|
272
|
+
self._display_cfat_summary(data)
|
273
|
+
return data
|
274
|
+
except json.JSONDecodeError:
|
275
|
+
return {"raw_output": result["stdout"]}
|
276
|
+
else:
|
277
|
+
console.print(f"[red]Error executing CFAT assessment: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
278
|
+
return {}
|
279
|
+
|
280
|
+
# Operations
|
281
|
+
def operate_ec2(self, action: str, instance_ids: List[str], profile: str = None, dry_run: bool = True) -> Dict[str, Any]:
|
282
|
+
"""
|
283
|
+
Perform EC2 operations (start, stop, terminate).
|
284
|
+
|
285
|
+
Args:
|
286
|
+
action: Action to perform (start, stop, terminate)
|
287
|
+
instance_ids: List of instance IDs
|
288
|
+
profile: AWS profile to use
|
289
|
+
dry_run: Perform dry run only
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
Dictionary with operation results
|
293
|
+
"""
|
294
|
+
cmd_parts = ["runbooks", "operate", "ec2", action]
|
295
|
+
cmd_parts.extend(["--instance-ids"] + instance_ids)
|
296
|
+
|
297
|
+
if profile or self.default_profile:
|
298
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
299
|
+
if dry_run:
|
300
|
+
cmd_parts.append("--dry-run")
|
301
|
+
|
302
|
+
command = " ".join(cmd_parts)
|
303
|
+
result = self._execute_command(command)
|
304
|
+
|
305
|
+
if result["success"]:
|
306
|
+
console.print(f"[green]✅ EC2 {action} operation completed successfully[/green]")
|
307
|
+
return {"success": True, "output": result["stdout"]}
|
308
|
+
else:
|
309
|
+
console.print(f"[red]❌ EC2 {action} operation failed: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
310
|
+
return {"success": False, "error": result.get("error", result.get("stderr"))}
|
311
|
+
|
312
|
+
# Organization Management
|
313
|
+
def org_list_ous(self, profile: str = None, output_format: str = "table") -> pd.DataFrame:
|
314
|
+
"""
|
315
|
+
List organizational units in AWS Organizations.
|
316
|
+
|
317
|
+
Args:
|
318
|
+
profile: AWS management profile
|
319
|
+
output_format: Output format (table, json)
|
320
|
+
|
321
|
+
Returns:
|
322
|
+
DataFrame with OU information
|
323
|
+
"""
|
324
|
+
cmd_parts = ["runbooks", "org", "list-ous"]
|
325
|
+
|
326
|
+
if profile or self.default_profile:
|
327
|
+
cmd_parts.extend(["--profile", profile or self.default_profile])
|
328
|
+
if output_format:
|
329
|
+
cmd_parts.extend(["--output", output_format])
|
330
|
+
|
331
|
+
command = " ".join(cmd_parts)
|
332
|
+
result = self._execute_command(command)
|
333
|
+
|
334
|
+
if result["success"]:
|
335
|
+
try:
|
336
|
+
if output_format == "json":
|
337
|
+
data = json.loads(result["stdout"])
|
338
|
+
df = pd.DataFrame(data.get("organizational_units", []))
|
339
|
+
else:
|
340
|
+
# Parse table output
|
341
|
+
df = pd.DataFrame({"output": [result["stdout"]]})
|
342
|
+
return df
|
343
|
+
except json.JSONDecodeError:
|
344
|
+
return pd.DataFrame({"output": [result["stdout"]]})
|
345
|
+
else:
|
346
|
+
console.print(f"[red]Error listing OUs: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
347
|
+
return pd.DataFrame()
|
348
|
+
|
349
|
+
# MCP Validation
|
350
|
+
def validate_mcp_servers(self, billing_profile: str = None) -> Dict[str, Any]:
|
351
|
+
"""
|
352
|
+
Validate MCP servers connectivity and accuracy.
|
353
|
+
|
354
|
+
Args:
|
355
|
+
billing_profile: Billing profile for validation
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
Dictionary with validation results
|
359
|
+
"""
|
360
|
+
cmd_parts = ["runbooks", "validate", "mcp-servers"]
|
361
|
+
|
362
|
+
if billing_profile or self.default_profile:
|
363
|
+
cmd_parts.extend(["--billing-profile", billing_profile or self.default_profile])
|
364
|
+
|
365
|
+
command = " ".join(cmd_parts)
|
366
|
+
result = self._execute_command(command)
|
367
|
+
|
368
|
+
if result["success"]:
|
369
|
+
try:
|
370
|
+
data = json.loads(result["stdout"])
|
371
|
+
self._display_mcp_validation_summary(data)
|
372
|
+
return data
|
373
|
+
except json.JSONDecodeError:
|
374
|
+
return {"raw_output": result["stdout"]}
|
375
|
+
else:
|
376
|
+
console.print(f"[red]Error validating MCP servers: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
|
377
|
+
return {}
|
378
|
+
|
379
|
+
# Rich Display Methods
|
380
|
+
def _display_inventory_summary(self, df: pd.DataFrame):
|
381
|
+
"""Display inventory results summary with Rich formatting."""
|
382
|
+
if df.empty:
|
383
|
+
console.print("[yellow]No inventory data to display[/yellow]")
|
384
|
+
return
|
385
|
+
|
386
|
+
table = Table(title="📊 Inventory Summary", box=box.ROUNDED)
|
387
|
+
table.add_column("Metric", style="cyan")
|
388
|
+
table.add_column("Count", justify="right", style="green")
|
389
|
+
|
390
|
+
total_resources = len(df)
|
391
|
+
resource_types = df.get("ResourceType", pd.Series()).nunique() if "ResourceType" in df.columns else 0
|
392
|
+
accounts = df.get("AccountId", pd.Series()).nunique() if "AccountId" in df.columns else 0
|
393
|
+
|
394
|
+
table.add_row("Total Resources", str(total_resources))
|
395
|
+
table.add_row("Resource Types", str(resource_types))
|
396
|
+
table.add_row("AWS Accounts", str(accounts))
|
397
|
+
|
398
|
+
console.print(table)
|
399
|
+
|
400
|
+
def _display_finops_summary(self, data: Dict[str, Any]):
|
401
|
+
"""Display FinOps analysis summary with Rich formatting."""
|
402
|
+
panel_content = []
|
403
|
+
|
404
|
+
if "total_cost" in data:
|
405
|
+
panel_content.append(f"💰 Total Monthly Cost: ${data['total_cost']:,.2f}")
|
406
|
+
if "potential_savings" in data:
|
407
|
+
panel_content.append(f"💸 Potential Savings: ${data['potential_savings']:,.2f}")
|
408
|
+
if "optimization_recommendations" in data:
|
409
|
+
panel_content.append(f"📋 Recommendations: {len(data['optimization_recommendations'])}")
|
410
|
+
|
411
|
+
content = "\n".join(panel_content) if panel_content else "FinOps analysis completed"
|
412
|
+
|
413
|
+
console.print(Panel(
|
414
|
+
content,
|
415
|
+
title="💰 FinOps Analysis Summary",
|
416
|
+
border_style="green"
|
417
|
+
))
|
418
|
+
|
419
|
+
def _display_security_summary(self, data: Dict[str, Any]):
|
420
|
+
"""Display security assessment summary with Rich formatting."""
|
421
|
+
panel_content = []
|
422
|
+
|
423
|
+
if "total_checks" in data:
|
424
|
+
panel_content.append(f"🔍 Total Checks: {data['total_checks']}")
|
425
|
+
if "passed_checks" in data:
|
426
|
+
panel_content.append(f"✅ Passed: {data['passed_checks']}")
|
427
|
+
if "failed_checks" in data:
|
428
|
+
panel_content.append(f"❌ Failed: {data['failed_checks']}")
|
429
|
+
if "compliance_score" in data:
|
430
|
+
panel_content.append(f"📊 Compliance Score: {data['compliance_score']}%")
|
431
|
+
|
432
|
+
content = "\n".join(panel_content) if panel_content else "Security assessment completed"
|
433
|
+
|
434
|
+
console.print(Panel(
|
435
|
+
content,
|
436
|
+
title="🔒 Security Assessment Summary",
|
437
|
+
border_style="red"
|
438
|
+
))
|
439
|
+
|
440
|
+
def _display_cfat_summary(self, data: Dict[str, Any]):
|
441
|
+
"""Display CFAT assessment summary with Rich formatting."""
|
442
|
+
panel_content = []
|
443
|
+
|
444
|
+
if "well_architected_score" in data:
|
445
|
+
panel_content.append(f"🏗️ Well-Architected Score: {data['well_architected_score']}%")
|
446
|
+
if "pillars_assessed" in data:
|
447
|
+
panel_content.append(f"📋 Pillars Assessed: {len(data['pillars_assessed'])}")
|
448
|
+
if "high_risk_findings" in data:
|
449
|
+
panel_content.append(f"⚠️ High Risk Findings: {data['high_risk_findings']}")
|
450
|
+
|
451
|
+
content = "\n".join(panel_content) if panel_content else "CFAT assessment completed"
|
452
|
+
|
453
|
+
console.print(Panel(
|
454
|
+
content,
|
455
|
+
title="🏗️ CFAT Assessment Summary",
|
456
|
+
border_style="blue"
|
457
|
+
))
|
458
|
+
|
459
|
+
def _display_mcp_validation_summary(self, data: Dict[str, Any]):
|
460
|
+
"""Display MCP validation summary with Rich formatting."""
|
461
|
+
panel_content = []
|
462
|
+
|
463
|
+
if "accuracy_rate" in data:
|
464
|
+
panel_content.append(f"🎯 Accuracy Rate: {data['accuracy_rate']}%")
|
465
|
+
if "servers_validated" in data:
|
466
|
+
panel_content.append(f"🖥️ Servers Validated: {data['servers_validated']}")
|
467
|
+
if "validation_time" in data:
|
468
|
+
panel_content.append(f"⏱️ Validation Time: {data['validation_time']}s")
|
469
|
+
|
470
|
+
content = "\n".join(panel_content) if panel_content else "MCP validation completed"
|
471
|
+
|
472
|
+
color = "green" if data.get("accuracy_rate", 0) >= 99.5 else "yellow"
|
473
|
+
|
474
|
+
console.print(Panel(
|
475
|
+
content,
|
476
|
+
title="🔍 MCP Validation Summary",
|
477
|
+
border_style=color
|
478
|
+
))
|