runbooks 0.7.7__py3-none-any.whl → 0.9.0__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.
Files changed (157) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +2 -2
  3. runbooks/cfat/README.md +12 -1
  4. runbooks/cfat/__init__.py +8 -4
  5. runbooks/cfat/assessment/collectors.py +171 -14
  6. runbooks/cfat/assessment/compliance.py +546 -522
  7. runbooks/cfat/assessment/runner.py +129 -10
  8. runbooks/cfat/models.py +6 -2
  9. runbooks/common/__init__.py +152 -0
  10. runbooks/common/accuracy_validator.py +1039 -0
  11. runbooks/common/context_logger.py +440 -0
  12. runbooks/common/cross_module_integration.py +594 -0
  13. runbooks/common/enhanced_exception_handler.py +1108 -0
  14. runbooks/common/enterprise_audit_integration.py +634 -0
  15. runbooks/common/logger.py +14 -0
  16. runbooks/common/mcp_integration.py +539 -0
  17. runbooks/common/performance_monitor.py +387 -0
  18. runbooks/common/profile_utils.py +216 -0
  19. runbooks/common/rich_utils.py +622 -0
  20. runbooks/enterprise/__init__.py +68 -0
  21. runbooks/enterprise/error_handling.py +411 -0
  22. runbooks/enterprise/logging.py +439 -0
  23. runbooks/enterprise/multi_tenant.py +583 -0
  24. runbooks/feedback/user_feedback_collector.py +440 -0
  25. runbooks/finops/README.md +129 -14
  26. runbooks/finops/__init__.py +22 -3
  27. runbooks/finops/account_resolver.py +279 -0
  28. runbooks/finops/accuracy_cross_validator.py +638 -0
  29. runbooks/finops/aws_client.py +721 -36
  30. runbooks/finops/budget_integration.py +313 -0
  31. runbooks/finops/cli.py +90 -33
  32. runbooks/finops/cost_processor.py +211 -37
  33. runbooks/finops/dashboard_router.py +900 -0
  34. runbooks/finops/dashboard_runner.py +1334 -399
  35. runbooks/finops/embedded_mcp_validator.py +288 -0
  36. runbooks/finops/enhanced_dashboard_runner.py +526 -0
  37. runbooks/finops/enhanced_progress.py +327 -0
  38. runbooks/finops/enhanced_trend_visualization.py +423 -0
  39. runbooks/finops/finops_dashboard.py +41 -0
  40. runbooks/finops/helpers.py +639 -323
  41. runbooks/finops/iam_guidance.py +400 -0
  42. runbooks/finops/markdown_exporter.py +466 -0
  43. runbooks/finops/multi_dashboard.py +1502 -0
  44. runbooks/finops/optimizer.py +396 -395
  45. runbooks/finops/profile_processor.py +2 -2
  46. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  47. runbooks/finops/runbooks.security.report_generator.log +0 -0
  48. runbooks/finops/runbooks.security.run_script.log +0 -0
  49. runbooks/finops/runbooks.security.security_export.log +0 -0
  50. runbooks/finops/service_mapping.py +195 -0
  51. runbooks/finops/single_dashboard.py +710 -0
  52. runbooks/finops/tests/__init__.py +19 -0
  53. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  54. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  55. runbooks/finops/tests/run_tests.py +305 -0
  56. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  57. runbooks/finops/tests/test_integration.py +477 -0
  58. runbooks/finops/tests/test_performance.py +380 -0
  59. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  60. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  61. runbooks/finops/tests/test_single_account_features.py +715 -0
  62. runbooks/finops/tests/validate_test_suite.py +220 -0
  63. runbooks/finops/types.py +1 -1
  64. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  65. runbooks/inventory/README.md +12 -1
  66. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  67. runbooks/inventory/collectors/aws_comprehensive.py +192 -185
  68. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  69. runbooks/inventory/core/collector.py +299 -12
  70. runbooks/inventory/list_ec2_instances.py +21 -20
  71. runbooks/inventory/list_ssm_parameters.py +31 -3
  72. runbooks/inventory/organizations_discovery.py +1315 -0
  73. runbooks/inventory/rich_inventory_display.py +360 -0
  74. runbooks/inventory/run_on_multi_accounts.py +32 -16
  75. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  76. runbooks/inventory/runbooks.security.run_script.log +0 -0
  77. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  78. runbooks/main.py +4171 -1615
  79. runbooks/metrics/dora_metrics_engine.py +1293 -0
  80. runbooks/monitoring/performance_monitor.py +433 -0
  81. runbooks/operate/README.md +394 -0
  82. runbooks/operate/__init__.py +2 -2
  83. runbooks/operate/base.py +291 -11
  84. runbooks/operate/deployment_framework.py +1032 -0
  85. runbooks/operate/deployment_validator.py +853 -0
  86. runbooks/operate/dynamodb_operations.py +10 -6
  87. runbooks/operate/ec2_operations.py +321 -11
  88. runbooks/operate/executive_dashboard.py +779 -0
  89. runbooks/operate/mcp_integration.py +750 -0
  90. runbooks/operate/nat_gateway_operations.py +1120 -0
  91. runbooks/operate/networking_cost_heatmap.py +685 -0
  92. runbooks/operate/privatelink_operations.py +940 -0
  93. runbooks/operate/s3_operations.py +10 -6
  94. runbooks/operate/vpc_endpoints.py +644 -0
  95. runbooks/operate/vpc_operations.py +1038 -0
  96. runbooks/remediation/README.md +489 -13
  97. runbooks/remediation/__init__.py +2 -2
  98. runbooks/remediation/acm_remediation.py +1 -1
  99. runbooks/remediation/base.py +1 -1
  100. runbooks/remediation/cloudtrail_remediation.py +1 -1
  101. runbooks/remediation/cognito_remediation.py +1 -1
  102. runbooks/remediation/commons.py +8 -4
  103. runbooks/remediation/dynamodb_remediation.py +1 -1
  104. runbooks/remediation/ec2_remediation.py +1 -1
  105. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  106. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  107. runbooks/remediation/kms_remediation.py +1 -1
  108. runbooks/remediation/lambda_remediation.py +1 -1
  109. runbooks/remediation/multi_account.py +1 -1
  110. runbooks/remediation/rds_remediation.py +1 -1
  111. runbooks/remediation/s3_block_public_access.py +1 -1
  112. runbooks/remediation/s3_enable_access_logging.py +1 -1
  113. runbooks/remediation/s3_encryption.py +1 -1
  114. runbooks/remediation/s3_remediation.py +1 -1
  115. runbooks/remediation/vpc_remediation.py +475 -0
  116. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
  117. runbooks/security/README.md +12 -1
  118. runbooks/security/__init__.py +166 -33
  119. runbooks/security/compliance_automation.py +634 -0
  120. runbooks/security/compliance_automation_engine.py +1021 -0
  121. runbooks/security/enterprise_security_framework.py +931 -0
  122. runbooks/security/enterprise_security_policies.json +293 -0
  123. runbooks/security/integration_test_enterprise_security.py +879 -0
  124. runbooks/security/module_security_integrator.py +641 -0
  125. runbooks/security/report_generator.py +10 -0
  126. runbooks/security/run_script.py +27 -5
  127. runbooks/security/security_baseline_tester.py +153 -27
  128. runbooks/security/security_export.py +456 -0
  129. runbooks/sre/README.md +472 -0
  130. runbooks/sre/__init__.py +33 -0
  131. runbooks/sre/mcp_reliability_engine.py +1049 -0
  132. runbooks/sre/performance_optimization_engine.py +1032 -0
  133. runbooks/sre/reliability_monitoring_framework.py +1011 -0
  134. runbooks/validation/__init__.py +10 -0
  135. runbooks/validation/benchmark.py +489 -0
  136. runbooks/validation/cli.py +368 -0
  137. runbooks/validation/mcp_validator.py +797 -0
  138. runbooks/vpc/README.md +478 -0
  139. runbooks/vpc/__init__.py +38 -0
  140. runbooks/vpc/config.py +212 -0
  141. runbooks/vpc/cost_engine.py +347 -0
  142. runbooks/vpc/heatmap_engine.py +605 -0
  143. runbooks/vpc/manager_interface.py +649 -0
  144. runbooks/vpc/networking_wrapper.py +1289 -0
  145. runbooks/vpc/rich_formatters.py +693 -0
  146. runbooks/vpc/tests/__init__.py +5 -0
  147. runbooks/vpc/tests/conftest.py +356 -0
  148. runbooks/vpc/tests/test_cli_integration.py +530 -0
  149. runbooks/vpc/tests/test_config.py +458 -0
  150. runbooks/vpc/tests/test_cost_engine.py +479 -0
  151. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  152. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/METADATA +175 -65
  153. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/RECORD +157 -60
  154. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
  155. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
  156. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
  157. {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,526 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced FinOps Dashboard Runner
4
+
5
+ This module provides enterprise-grade FinOps dashboard capabilities including:
6
+ - Multi-profile AWS cost analysis with Rich console formatting
7
+ - Advanced audit reporting with PDF/CSV/JSON export
8
+ - Resource utilization tracking and optimization recommendations
9
+ - Budget monitoring and alerting integration
10
+ - Trend analysis and forecasting capabilities
11
+ """
12
+
13
+ import argparse
14
+ import csv
15
+ import json
16
+ from collections import defaultdict
17
+ from datetime import datetime, timedelta
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Tuple, Union
20
+
21
+ import boto3
22
+ from rich import box
23
+ from rich.console import Console
24
+ from rich.panel import Panel
25
+ from rich.progress import Progress, SpinnerColumn, TextColumn, track
26
+ from rich.status import Status
27
+ from rich.table import Column, Table
28
+ from rich.tree import Tree
29
+
30
+ from ..common.rich_utils import get_console
31
+
32
+ # FinOpsConfig dependency removed - using simple dict configuration instead
33
+
34
+ console = Console()
35
+
36
+
37
+ class EnhancedFinOpsDashboard:
38
+ """Enhanced FinOps Dashboard with production-tested capabilities from runbooks finops"""
39
+
40
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
41
+ self.config = config or {}
42
+ self.console = Console()
43
+ self.rich_console = self.console # Use the console instance directly
44
+
45
+ # Export directory setup
46
+ self.export_dir = Path("artifacts/finops-exports")
47
+ self.export_dir.mkdir(parents=True, exist_ok=True)
48
+
49
+ def get_aws_profiles(self) -> List[str]:
50
+ """Get available AWS profiles from AWS CLI configuration"""
51
+ try:
52
+ import configparser
53
+ import os
54
+
55
+ aws_config_path = os.path.expanduser("~/.aws/config")
56
+ aws_credentials_path = os.path.expanduser("~/.aws/credentials")
57
+
58
+ profiles = set()
59
+
60
+ # Parse AWS config file
61
+ if os.path.exists(aws_config_path):
62
+ config = configparser.ConfigParser()
63
+ config.read(aws_config_path)
64
+ for section in config.sections():
65
+ if section.startswith("profile "):
66
+ profiles.add(section.replace("profile ", ""))
67
+ elif section == "default":
68
+ profiles.add("default")
69
+
70
+ # Parse AWS credentials file
71
+ if os.path.exists(aws_credentials_path):
72
+ credentials = configparser.ConfigParser()
73
+ credentials.read(aws_credentials_path)
74
+ profiles.update(credentials.sections())
75
+
76
+ return sorted(list(profiles))
77
+
78
+ except Exception as e:
79
+ console.print(f"āš ļø Error reading AWS profiles: {e}", style="yellow")
80
+ return []
81
+
82
+ def get_account_info(self, profile: str) -> Dict[str, Any]:
83
+ """Get AWS account information for a profile"""
84
+ try:
85
+ session = boto3.Session(profile_name=profile)
86
+ sts = session.client("sts")
87
+
88
+ identity = sts.get_caller_identity()
89
+
90
+ return {
91
+ "account_id": identity["Account"],
92
+ "user_arn": identity["Arn"],
93
+ "user_id": identity["UserId"],
94
+ "profile": profile,
95
+ "status": "active",
96
+ }
97
+
98
+ except Exception as e:
99
+ return {"account_id": "N/A", "profile": profile, "status": "error", "error": str(e)}
100
+
101
+ def get_resource_audit_data(self, profile: str, regions: Optional[List[str]] = None) -> Dict[str, Any]:
102
+ """
103
+ Get comprehensive resource audit data for a profile
104
+
105
+ Enhanced with additional resource types and cost impact analysis
106
+ """
107
+ audit_data = {
108
+ "profile": profile,
109
+ "account_info": self.get_account_info(profile),
110
+ "untagged_resources": 0,
111
+ "stopped_instances": 0,
112
+ "unused_volumes": 0,
113
+ "unused_eips": 0,
114
+ "budget_alerts": 0,
115
+ "cost_optimization_opportunities": [],
116
+ "regional_breakdown": {},
117
+ "total_potential_savings": 0.0,
118
+ }
119
+
120
+ if audit_data["account_info"]["status"] == "error":
121
+ return audit_data
122
+
123
+ try:
124
+ session = boto3.Session(profile_name=profile)
125
+
126
+ # Default to common regions if none specified
127
+ if not regions:
128
+ regions = ["us-east-1", "us-west-2", "eu-west-1"]
129
+
130
+ for region in regions:
131
+ region_data = self._audit_region_resources(session, region)
132
+ audit_data["regional_breakdown"][region] = region_data
133
+
134
+ # Aggregate data
135
+ audit_data["untagged_resources"] += region_data["untagged_resources"]
136
+ audit_data["stopped_instances"] += region_data["stopped_instances"]
137
+ audit_data["unused_volumes"] += region_data["unused_volumes"]
138
+ audit_data["unused_eips"] += region_data["unused_eips"]
139
+ audit_data["total_potential_savings"] += region_data["potential_savings"]
140
+ audit_data["cost_optimization_opportunities"].extend(region_data["optimization_opportunities"])
141
+
142
+ # Get budget information
143
+ audit_data["budget_alerts"] = self._get_budget_alerts(session)
144
+
145
+ except Exception as e:
146
+ console.print(f"āš ļø Error auditing resources for {profile}: {e}", style="yellow")
147
+
148
+ return audit_data
149
+
150
+ def _audit_region_resources(self, session: boto3.Session, region: str) -> Dict[str, Any]:
151
+ """Audit resources in a specific region"""
152
+ region_data = {
153
+ "region": region,
154
+ "untagged_resources": 0,
155
+ "stopped_instances": 0,
156
+ "unused_volumes": 0,
157
+ "unused_eips": 0,
158
+ "potential_savings": 0.0,
159
+ "optimization_opportunities": [],
160
+ }
161
+
162
+ try:
163
+ ec2 = session.client("ec2", region_name=region)
164
+
165
+ # Get stopped EC2 instances
166
+ instances_response = ec2.describe_instances(
167
+ Filters=[{"Name": "instance-state-name", "Values": ["stopped"]}]
168
+ )
169
+
170
+ stopped_instances = []
171
+ for reservation in instances_response["Reservations"]:
172
+ for instance in reservation["Instances"]:
173
+ stopped_instances.append(
174
+ {
175
+ "instance_id": instance["InstanceId"],
176
+ "instance_type": instance["InstanceType"],
177
+ "launch_time": instance.get("LaunchTime"),
178
+ "tags": instance.get("Tags", []),
179
+ }
180
+ )
181
+
182
+ region_data["stopped_instances"] = len(stopped_instances)
183
+
184
+ # Calculate potential savings from stopped instances (rough estimate)
185
+ # Assume average $50/month per stopped instance in savings opportunity
186
+ region_data["potential_savings"] += len(stopped_instances) * 50.0
187
+
188
+ if stopped_instances:
189
+ region_data["optimization_opportunities"].append(
190
+ {
191
+ "type": "stopped_instances",
192
+ "count": len(stopped_instances),
193
+ "description": f"{len(stopped_instances)} stopped EC2 instances - consider termination",
194
+ "potential_savings": len(stopped_instances) * 50.0,
195
+ "priority": "high",
196
+ }
197
+ )
198
+
199
+ # Get unused EBS volumes
200
+ volumes_response = ec2.describe_volumes(Filters=[{"Name": "status", "Values": ["available"]}])
201
+
202
+ unused_volumes = volumes_response["Volumes"]
203
+ region_data["unused_volumes"] = len(unused_volumes)
204
+
205
+ # Note: EBS cost calculation requires real AWS Cost Explorer pricing data
206
+ # Hardcoded pricing estimates removed per compliance requirements
207
+ volume_savings = 0 # Cannot calculate without real AWS pricing API
208
+ region_data["potential_savings"] += volume_savings
209
+
210
+ if unused_volumes:
211
+ region_data["optimization_opportunities"].append(
212
+ {
213
+ "type": "unused_volumes",
214
+ "count": len(unused_volumes),
215
+ "description": f"{len(unused_volumes)} unused EBS volumes",
216
+ "potential_savings": volume_savings,
217
+ "priority": "medium",
218
+ }
219
+ )
220
+
221
+ # Get unused Elastic IPs
222
+ eips_response = ec2.describe_addresses()
223
+ unused_eips = [eip for eip in eips_response["Addresses"] if "InstanceId" not in eip]
224
+ region_data["unused_eips"] = len(unused_eips)
225
+
226
+ # Unused EIP cost: $3.65/month each
227
+ eip_savings = len(unused_eips) * 3.65
228
+ region_data["potential_savings"] += eip_savings
229
+
230
+ if unused_eips:
231
+ region_data["optimization_opportunities"].append(
232
+ {
233
+ "type": "unused_eips",
234
+ "count": len(unused_eips),
235
+ "description": f"{len(unused_eips)} unused Elastic IPs",
236
+ "potential_savings": eip_savings,
237
+ "priority": "high",
238
+ }
239
+ )
240
+
241
+ # Count untagged resources (simplified check)
242
+ untagged_count = 0
243
+ for instance in stopped_instances:
244
+ if not instance["tags"]:
245
+ untagged_count += 1
246
+ for volume in unused_volumes:
247
+ if not volume.get("Tags"):
248
+ untagged_count += 1
249
+
250
+ region_data["untagged_resources"] = untagged_count
251
+
252
+ except Exception as e:
253
+ console.print(f"āš ļø Error auditing {region}: {e}", style="yellow")
254
+
255
+ return region_data
256
+
257
+ def _get_budget_alerts(self, session: boto3.Session) -> int:
258
+ """Get budget alert count"""
259
+ try:
260
+ budgets = session.client("budgets")
261
+
262
+ # Get account ID for budgets API
263
+ sts = session.client("sts")
264
+ account_id = sts.get_caller_identity()["Account"]
265
+
266
+ response = budgets.describe_budgets(AccountId=account_id)
267
+ return len(response.get("Budgets", []))
268
+
269
+ except Exception:
270
+ return 0 # Budgets API might not be accessible
271
+
272
+ def generate_audit_report(
273
+ self, profiles: Optional[List[str]] = None, regions: Optional[List[str]] = None
274
+ ) -> Dict[str, Any]:
275
+ """Generate comprehensive audit report for specified profiles"""
276
+
277
+ if not profiles:
278
+ profiles = self.get_aws_profiles()
279
+ if not profiles:
280
+ console.print("āŒ No AWS profiles found", style="red")
281
+ return {}
282
+
283
+ audit_results = {
284
+ "report_metadata": {
285
+ "generated_at": datetime.now().isoformat(),
286
+ "profiles_analyzed": len(profiles),
287
+ "regions_analyzed": len(regions) if regions else 3,
288
+ "report_type": "comprehensive_audit",
289
+ },
290
+ "profile_data": {},
291
+ "summary": {
292
+ "total_untagged_resources": 0,
293
+ "total_stopped_instances": 0,
294
+ "total_unused_volumes": 0,
295
+ "total_unused_eips": 0,
296
+ "total_budget_alerts": 0,
297
+ "total_potential_savings": 0.0,
298
+ "optimization_opportunities": [],
299
+ },
300
+ }
301
+
302
+ with Progress(
303
+ SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=console
304
+ ) as progress:
305
+ for profile in profiles:
306
+ task = progress.add_task(f"Auditing profile {profile}...", total=None)
307
+
308
+ profile_data = self.get_resource_audit_data(profile, regions)
309
+ audit_results["profile_data"][profile] = profile_data
310
+
311
+ # Aggregate summary data
312
+ summary = audit_results["summary"]
313
+ summary["total_untagged_resources"] += profile_data["untagged_resources"]
314
+ summary["total_stopped_instances"] += profile_data["stopped_instances"]
315
+ summary["total_unused_volumes"] += profile_data["unused_volumes"]
316
+ summary["total_unused_eips"] += profile_data["unused_eips"]
317
+ summary["total_budget_alerts"] += profile_data["budget_alerts"]
318
+ summary["total_potential_savings"] += profile_data["total_potential_savings"]
319
+ summary["optimization_opportunities"].extend(profile_data["cost_optimization_opportunities"])
320
+
321
+ progress.remove_task(task)
322
+
323
+ return audit_results
324
+
325
+ def display_audit_report(self, audit_results: Dict[str, Any]):
326
+ """Display audit report with enhanced Rich formatting"""
327
+
328
+ summary = audit_results["summary"]
329
+ profile_data = audit_results["profile_data"]
330
+
331
+ # Report header
332
+ header_panel = Panel.fit(
333
+ f"[bold bright_cyan]šŸ¢ AWS FinOps Comprehensive Audit Report[/bold bright_cyan]\n\n"
334
+ f"šŸ“Š Profiles Analyzed: [yellow]{len(profile_data)}[/yellow]\n"
335
+ f"šŸ•’ Generated: [green]{audit_results['report_metadata']['generated_at'][:19]}[/green]\n"
336
+ f"šŸ’° Total Savings Potential: [bold green]${summary['total_potential_savings']:.2f}/month[/bold green]",
337
+ title="Audit Report",
338
+ style="bright_cyan",
339
+ )
340
+ console.print(header_panel)
341
+
342
+ # Summary table
343
+ summary_table = Table(title="šŸ“ˆ Executive Summary", box=box.ASCII_DOUBLE_HEAD, style="bright_cyan")
344
+ summary_table.add_column("Metric", style="cyan", width=25)
345
+ summary_table.add_column("Count", style="yellow", width=10)
346
+ summary_table.add_column("Impact", style="green", width=20)
347
+
348
+ summary_table.add_row("Untagged Resources", str(summary["total_untagged_resources"]), "Compliance Risk")
349
+ summary_table.add_row(
350
+ "Stopped Instances",
351
+ str(summary["total_stopped_instances"]),
352
+ f"${summary['total_stopped_instances'] * 50:.0f}/month potential",
353
+ )
354
+ summary_table.add_row("Unused Volumes", str(summary["total_unused_volumes"]), "Storage waste")
355
+ summary_table.add_row(
356
+ "Unused EIPs", str(summary["total_unused_eips"]), f"${summary['total_unused_eips'] * 3.65:.0f}/month waste"
357
+ )
358
+ summary_table.add_row("Budget Alerts", str(summary["total_budget_alerts"]), "Monitoring coverage")
359
+
360
+ console.print(summary_table)
361
+
362
+ # Profile-specific table
363
+ profile_table = Table(
364
+ title="šŸ‘„ Profile-Specific Analysis", show_lines=True, box=box.ASCII_DOUBLE_HEAD, style="bright_cyan"
365
+ )
366
+
367
+ profile_table.add_column("Profile", justify="center", width=20)
368
+ profile_table.add_column("Account ID", justify="center", width=15)
369
+ profile_table.add_column("Untagged", width=10)
370
+ profile_table.add_column("Stopped EC2", width=12)
371
+ profile_table.add_column("Unused Vol", width=12)
372
+ profile_table.add_column("Unused EIP", width=12)
373
+ profile_table.add_column("Savings", width=12)
374
+
375
+ for profile, data in profile_data.items():
376
+ account_info = data["account_info"]
377
+ profile_table.add_row(
378
+ profile,
379
+ account_info["account_id"] if account_info["status"] == "active" else "ERROR",
380
+ str(data["untagged_resources"]),
381
+ str(data["stopped_instances"]),
382
+ str(data["unused_volumes"]),
383
+ str(data["unused_eips"]),
384
+ f"${data['total_potential_savings']:.0f}",
385
+ )
386
+
387
+ console.print(profile_table)
388
+
389
+ # Top optimization opportunities
390
+ if summary["optimization_opportunities"]:
391
+ console.print("\n[bold blue]šŸŽÆ Top Optimization Opportunities[/bold blue]")
392
+
393
+ # Sort by potential savings
394
+ sorted_opportunities = sorted(
395
+ summary["optimization_opportunities"], key=lambda x: x.get("potential_savings", 0), reverse=True
396
+ )
397
+
398
+ for i, opp in enumerate(sorted_opportunities[:10], 1): # Top 10
399
+ priority_color = {"high": "red", "medium": "yellow", "low": "green"}
400
+ color = priority_color.get(opp.get("priority", "low"), "white")
401
+
402
+ console.print(
403
+ f"{i:2d}. [bold {color}]{opp['description']}[/bold {color}] "
404
+ f"([green]${opp.get('potential_savings', 0):.0f}/month[/green])"
405
+ )
406
+
407
+ def export_audit_report(
408
+ self, audit_results: Dict[str, Any], formats: List[str] = ["json", "csv"]
409
+ ) -> Dict[str, str]:
410
+ """Export audit report in multiple formats"""
411
+
412
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
413
+ export_files = {}
414
+
415
+ # JSON export
416
+ if "json" in formats:
417
+ json_file = self.export_dir / f"finops_audit_report_{timestamp}.json"
418
+ with open(json_file, "w") as f:
419
+ json.dump(audit_results, f, indent=2, default=str)
420
+ export_files["json"] = str(json_file)
421
+
422
+ # CSV export
423
+ if "csv" in formats:
424
+ csv_file = self.export_dir / f"finops_audit_summary_{timestamp}.csv"
425
+ with open(csv_file, "w", newline="") as f:
426
+ writer = csv.writer(f)
427
+
428
+ # Header
429
+ writer.writerow(
430
+ [
431
+ "Profile",
432
+ "Account_ID",
433
+ "Untagged_Resources",
434
+ "Stopped_Instances",
435
+ "Unused_Volumes",
436
+ "Unused_EIPs",
437
+ "Budget_Alerts",
438
+ "Potential_Savings_Monthly",
439
+ ]
440
+ )
441
+
442
+ # Data rows
443
+ for profile, data in audit_results["profile_data"].items():
444
+ writer.writerow(
445
+ [
446
+ profile,
447
+ data["account_info"]["account_id"],
448
+ data["untagged_resources"],
449
+ data["stopped_instances"],
450
+ data["unused_volumes"],
451
+ data["unused_eips"],
452
+ data["budget_alerts"],
453
+ f"${data['total_potential_savings']:.2f}",
454
+ ]
455
+ )
456
+
457
+ export_files["csv"] = str(csv_file)
458
+
459
+ return export_files
460
+
461
+ def run_comprehensive_audit(
462
+ self,
463
+ profiles: Optional[List[str]] = None,
464
+ regions: Optional[List[str]] = None,
465
+ export_formats: List[str] = ["json", "csv"],
466
+ display_report: bool = True,
467
+ ) -> Dict[str, Any]:
468
+ """Run comprehensive FinOps audit with reporting and export"""
469
+
470
+ console.print("[bold bright_cyan]šŸš€ Starting Enhanced FinOps Audit...[/bold bright_cyan]")
471
+
472
+ # Generate audit data
473
+ audit_results = self.generate_audit_report(profiles, regions)
474
+
475
+ if not audit_results:
476
+ console.print("āŒ No audit data generated", style="red")
477
+ return {}
478
+
479
+ # Display report
480
+ if display_report:
481
+ self.display_audit_report(audit_results)
482
+
483
+ # Export results
484
+ if export_formats:
485
+ console.print(f"\nšŸ“„ Exporting report in formats: {', '.join(export_formats)}")
486
+ export_files = self.export_audit_report(audit_results, export_formats)
487
+
488
+ console.print("āœ… Export completed:")
489
+ for format_type, file_path in export_files.items():
490
+ console.print(f" šŸ“ {format_type.upper()}: {file_path}")
491
+
492
+ # Summary of potential savings
493
+ total_savings = audit_results["summary"]["total_potential_savings"]
494
+ if total_savings > 0:
495
+ annual_savings = total_savings * 12
496
+ console.print(f"\nšŸ’° [bold green]Total Optimization Potential:[/bold green]")
497
+ console.print(f" Monthly: [yellow]${total_savings:.2f}[/yellow]")
498
+ console.print(f" Annual: [green]${annual_savings:.2f}[/green]")
499
+
500
+ return audit_results
501
+
502
+
503
+ # CLI integration functions
504
+ def enhanced_audit_cli(
505
+ profiles: Optional[str] = None,
506
+ regions: Optional[str] = None,
507
+ export_formats: str = "json,csv",
508
+ output_dir: Optional[str] = None,
509
+ ) -> None:
510
+ """CLI command for enhanced FinOps audit"""
511
+
512
+ profile_list = profiles.split(",") if profiles else None
513
+ region_list = regions.split(",") if regions else None
514
+ format_list = export_formats.split(",") if export_formats else ["json"]
515
+
516
+ dashboard = EnhancedFinOpsDashboard()
517
+
518
+ if output_dir:
519
+ dashboard.export_dir = Path(output_dir)
520
+ dashboard.export_dir.mkdir(parents=True, exist_ok=True)
521
+
522
+ audit_results = dashboard.run_comprehensive_audit(
523
+ profiles=profile_list, regions=region_list, export_formats=format_list, display_report=True
524
+ )
525
+
526
+ return audit_results