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,313 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced Budget Integration - Real AWS Budgets API Integration
4
+
5
+ This module provides comprehensive AWS Budgets API integration for enhanced
6
+ Budget Status column values, replacing placeholder data with real budget
7
+ information, alerts, and utilization tracking.
8
+
9
+ Features:
10
+ - Real AWS Budgets API integration
11
+ - Budget utilization calculations with visual indicators
12
+ - Budget alert thresholds and notifications
13
+ - Forecast vs actual spend analysis
14
+ - Rich CLI formatting for budget status
15
+ - Performance optimized for multi-account operations
16
+
17
+ Author: CloudOps Runbooks Team
18
+ Version: 0.8.0
19
+ """
20
+
21
+ import json
22
+ from datetime import datetime, timedelta
23
+ from typing import Any, Dict, List, Optional, Tuple
24
+
25
+ import boto3
26
+ from botocore.exceptions import ClientError
27
+ from rich.console import Console
28
+
29
+ from ..common.rich_utils import (
30
+ STATUS_INDICATORS,
31
+ format_cost,
32
+ print_info,
33
+ print_warning,
34
+ )
35
+ from ..common.rich_utils import (
36
+ console as rich_console,
37
+ )
38
+
39
+
40
+ class EnhancedBudgetAnalyzer:
41
+ """
42
+ Enhanced AWS Budgets API integration for real-time budget analysis.
43
+
44
+ Provides comprehensive budget status, utilization tracking, and alert
45
+ management across single and multi-account AWS environments.
46
+ """
47
+
48
+ def __init__(self, console: Optional[Console] = None):
49
+ self.console = console or rich_console
50
+
51
+ def get_enhanced_budget_status(
52
+ self, session: boto3.Session, current_cost: float, account_id: str
53
+ ) -> Dict[str, Any]:
54
+ """
55
+ Get enhanced budget status with real AWS Budgets API data.
56
+
57
+ Args:
58
+ session: AWS session with budget access
59
+ current_cost: Current month cost for comparison
60
+ account_id: AWS account ID for budget identification
61
+
62
+ Returns:
63
+ Dict containing enhanced budget information with Rich formatting
64
+ """
65
+ try:
66
+ budgets_client = session.client("budgets")
67
+
68
+ # Get all budgets for the account
69
+ budgets_response = budgets_client.describe_budgets(AccountId=account_id)
70
+ budgets = budgets_response.get("Budgets", [])
71
+
72
+ if not budgets:
73
+ return {
74
+ "status": "no_budget",
75
+ "display": "[dim]No Budget Set[/]",
76
+ "utilization": 0,
77
+ "details": "No budgets configured for this account",
78
+ "recommendation": "Consider setting up budget alerts",
79
+ }
80
+
81
+ # Analyze primary budget (or cost budget if multiple exist)
82
+ primary_budget = self._select_primary_budget(budgets)
83
+
84
+ if not primary_budget:
85
+ return {
86
+ "status": "no_budget",
87
+ "display": "[dim]No Cost Budget[/]",
88
+ "utilization": 0,
89
+ "details": "No cost-based budgets found",
90
+ "recommendation": "Create monthly cost budget",
91
+ }
92
+
93
+ # Get budget utilization data
94
+ utilization_response = budgets_client.describe_budget_performance_history(
95
+ AccountId=account_id, BudgetName=primary_budget["BudgetName"]
96
+ )
97
+
98
+ # Calculate enhanced budget status
99
+ budget_analysis = self._analyze_budget_performance(primary_budget, utilization_response, current_cost)
100
+
101
+ return budget_analysis
102
+
103
+ except ClientError as e:
104
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
105
+ if error_code == "AccessDeniedException":
106
+ return {
107
+ "status": "access_denied",
108
+ "display": "[yellow]⚠️ Access Denied[/]",
109
+ "utilization": 0,
110
+ "details": "No budget read permissions",
111
+ "recommendation": "Grant budgets:ViewBudget permission",
112
+ }
113
+ else:
114
+ print_warning(f"Budget API error: {error_code}")
115
+ return self._create_estimated_budget_status(current_cost)
116
+
117
+ except Exception as e:
118
+ print_warning(f"Budget analysis failed: {str(e)[:50]}")
119
+ return self._create_estimated_budget_status(current_cost)
120
+
121
+ def _select_primary_budget(self, budgets: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
122
+ """Select the primary budget for analysis (cost budget preferred)."""
123
+
124
+ # Prefer cost budgets over usage budgets
125
+ cost_budgets = [b for b in budgets if b.get("BudgetType") == "COST"]
126
+ if cost_budgets:
127
+ # Prefer monthly budgets
128
+ monthly_budgets = [b for b in cost_budgets if b.get("TimeUnit") == "MONTHLY"]
129
+ if monthly_budgets:
130
+ return monthly_budgets[0] # First monthly cost budget
131
+ return cost_budgets[0] # First cost budget
132
+
133
+ # Fallback to any budget
134
+ return budgets[0] if budgets else None
135
+
136
+ def _analyze_budget_performance(
137
+ self, budget: Dict[str, Any], performance_history: Dict[str, Any], current_cost: float
138
+ ) -> Dict[str, Any]:
139
+ """Analyze budget performance and generate enhanced status."""
140
+
141
+ budget_limit = float(budget.get("BudgetLimit", {}).get("Amount", 0))
142
+ budget_name = budget.get("BudgetName", "Unknown")
143
+
144
+ if budget_limit == 0:
145
+ return {
146
+ "status": "no_limit",
147
+ "display": "[dim]Unlimited Budget[/]",
148
+ "utilization": 0,
149
+ "details": f'Budget "{budget_name}" has no spending limit',
150
+ "recommendation": "Set budget limit for cost control",
151
+ }
152
+
153
+ # Calculate utilization
154
+ utilization_percent = (current_cost / budget_limit) * 100
155
+
156
+ # Get alerts configuration if available
157
+ alerts_info = self._analyze_budget_alerts(budget)
158
+
159
+ # Generate status based on utilization
160
+ if utilization_percent >= 100:
161
+ return {
162
+ "status": "over_budget",
163
+ "display": f"[red]🚨 Over Budget[/]\n[red]{utilization_percent:.0f}% ({format_cost(current_cost)}/{format_cost(budget_limit)})[/]",
164
+ "utilization": utilization_percent,
165
+ "details": f"Exceeded budget by {format_cost(current_cost - budget_limit)}",
166
+ "recommendation": "Immediate cost review required",
167
+ "alerts": alerts_info,
168
+ }
169
+ elif utilization_percent >= 90:
170
+ return {
171
+ "status": "critical",
172
+ "display": f"[red]⚠️ Critical[/]\n[red]{utilization_percent:.0f}% ({format_cost(current_cost)}/{format_cost(budget_limit)})[/]",
173
+ "utilization": utilization_percent,
174
+ "details": f"Approaching budget limit - {format_cost(budget_limit - current_cost)} remaining",
175
+ "recommendation": "Review and optimize high-cost services",
176
+ "alerts": alerts_info,
177
+ }
178
+ elif utilization_percent >= 75:
179
+ return {
180
+ "status": "warning",
181
+ "display": f"[yellow]⚠️ Warning[/]\n[yellow]{utilization_percent:.0f}% ({format_cost(current_cost)}/{format_cost(budget_limit)})[/]",
182
+ "utilization": utilization_percent,
183
+ "details": f"75% of budget used - {format_cost(budget_limit - current_cost)} remaining",
184
+ "recommendation": "Monitor spending closely",
185
+ "alerts": alerts_info,
186
+ }
187
+ elif utilization_percent >= 50:
188
+ return {
189
+ "status": "moderate",
190
+ "display": f"[cyan]πŸ“Š On Track[/]\n[cyan]{utilization_percent:.0f}% ({format_cost(current_cost)}/{format_cost(budget_limit)})[/]",
191
+ "utilization": utilization_percent,
192
+ "details": f"Moderate usage - {format_cost(budget_limit - current_cost)} remaining",
193
+ "recommendation": "Continue monitoring",
194
+ "alerts": alerts_info,
195
+ }
196
+ else:
197
+ return {
198
+ "status": "under_budget",
199
+ "display": f"[green]βœ… Under Budget[/]\n[green]{utilization_percent:.0f}% ({format_cost(current_cost)}/{format_cost(budget_limit)})[/]",
200
+ "utilization": utilization_percent,
201
+ "details": f"Low utilization - {format_cost(budget_limit - current_cost)} available",
202
+ "recommendation": "Budget utilization is low",
203
+ "alerts": alerts_info,
204
+ }
205
+
206
+ def _analyze_budget_alerts(self, budget: Dict[str, Any]) -> Dict[str, Any]:
207
+ """Analyze budget alert configuration."""
208
+ # This would analyze budget alerts if available in the budget configuration
209
+ # For now, return basic alert info
210
+ return {"configured": False, "thresholds": [], "notification_methods": []}
211
+
212
+ def _create_estimated_budget_status(self, current_cost: float) -> Dict[str, Any]:
213
+ """Create estimated budget status when real budget data is unavailable."""
214
+
215
+ # Simple heuristic-based budget estimation
216
+ if current_cost == 0:
217
+ return {
218
+ "status": "no_usage",
219
+ "display": "[dim]No Usage[/]",
220
+ "utilization": 0,
221
+ "details": "No current usage detected",
222
+ "recommendation": "Account appears inactive",
223
+ }
224
+ elif current_cost < 100:
225
+ return {
226
+ "status": "low_cost",
227
+ "display": f"[green]πŸ’° Low Cost[/]\n[green]{format_cost(current_cost)}/month[/]",
228
+ "utilization": 0,
229
+ "details": "Low monthly spend detected",
230
+ "recommendation": "Consider setting budget alerts",
231
+ }
232
+ elif current_cost < 1000:
233
+ return {
234
+ "status": "moderate_cost",
235
+ "display": f"[yellow]πŸ“Š Moderate[/]\n[yellow]{format_cost(current_cost)}/month[/]",
236
+ "utilization": 0,
237
+ "details": "Moderate monthly spend",
238
+ "recommendation": "Set budget limits for cost control",
239
+ }
240
+ else:
241
+ return {
242
+ "status": "high_cost",
243
+ "display": f"[red]πŸ’Έ High Cost[/]\n[red]{format_cost(current_cost)}/month[/]",
244
+ "utilization": 0,
245
+ "details": "High monthly spend detected",
246
+ "recommendation": "Budget management recommended",
247
+ }
248
+
249
+ def get_budget_forecast(
250
+ self, session: boto3.Session, account_id: str, budget_name: str
251
+ ) -> Optional[Dict[str, Any]]:
252
+ """Get budget forecast information for trend analysis."""
253
+ try:
254
+ budgets_client = session.client("budgets")
255
+
256
+ # Get budget forecast (if available)
257
+ forecast_response = budgets_client.describe_budget_performance_history(
258
+ AccountId=account_id,
259
+ BudgetName=budget_name,
260
+ TimePeriod={"Start": datetime.now() - timedelta(days=90), "End": datetime.now()},
261
+ )
262
+
263
+ # Process forecast data
264
+ performance_history = forecast_response.get("BudgetPerformanceHistory", {})
265
+
266
+ return {
267
+ "forecast_available": True,
268
+ "performance_history": performance_history,
269
+ "trend_analysis": self._analyze_spending_trends(performance_history),
270
+ }
271
+
272
+ except Exception as e:
273
+ print_warning(f"Budget forecast failed: {str(e)[:50]}")
274
+ return None
275
+
276
+ def _analyze_spending_trends(self, performance_history: Dict[str, Any]) -> Dict[str, Any]:
277
+ """Analyze spending trends from budget performance history."""
278
+ # Placeholder for trend analysis logic
279
+ return {"trend_direction": "stable", "trend_confidence": "medium", "forecast_accuracy": "unknown"}
280
+
281
+
282
+ def create_budget_analyzer(console: Optional[Console] = None) -> EnhancedBudgetAnalyzer:
283
+ """Factory function to create enhanced budget analyzer."""
284
+ return EnhancedBudgetAnalyzer(console=console)
285
+
286
+
287
+ def get_enhanced_budget_status_for_profile(profile: str, current_cost: float) -> Dict[str, Any]:
288
+ """
289
+ Convenience function to get budget status for a specific profile.
290
+
291
+ Args:
292
+ profile: AWS profile name
293
+ current_cost: Current month cost
294
+
295
+ Returns:
296
+ Enhanced budget status dictionary
297
+ """
298
+ try:
299
+ session = boto3.Session(profile_name=profile)
300
+ sts = session.client("sts")
301
+ account_id = sts.get_caller_identity()["Account"]
302
+
303
+ analyzer = create_budget_analyzer()
304
+ return analyzer.get_enhanced_budget_status(session, current_cost, account_id)
305
+
306
+ except Exception as e:
307
+ return {
308
+ "status": "error",
309
+ "display": "[red]Error[/]",
310
+ "utilization": 0,
311
+ "details": f"Budget analysis failed: {str(e)[:50]}",
312
+ "recommendation": "Check AWS credentials and permissions",
313
+ }
runbooks/finops/cli.py CHANGED
@@ -10,38 +10,38 @@ from runbooks.finops.helpers import load_config_file
10
10
 
11
11
  console = Console()
12
12
 
13
- __version__ = "0.7.6"
13
+ __version__ = "0.7.8"
14
14
 
15
15
 
16
16
  def welcome_banner() -> None:
17
17
  banner = rf"""
18
18
  [bold red]
19
- /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$$ /$$ /$$$$$$
20
- /$$__ $$| $$ /$ | $$ /$$__ $$ | $$_____/|__/ /$$__ $$
21
- | $$ \ $$| $$ /$$$| $$| $$ \__/ | $$ /$$ /$$$$$$$ | $$ \ $$ /$$$$$$ /$$$$$$$
22
- | $$$$$$$$| $$/$$ $$ $$| $$$$$$ | $$$$$ | $$| $$__ $$| $$ | $$ /$$__ $$ /$$_____/
23
- | $$__ $$| $$$$_ $$$$ \____ $$ | $$__/ | $$| $$ \ $$| $$ | $$| $$ \ $$| $$$$$$
24
- | $$ | $$| $$$/ \ $$$ /$$ \ $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$ \____ $$
25
- | $$ | $$| $$/ \ $$| $$$$$$/ | $$ | $$| $$ | $$| $$$$$$/| $$$$$$$/ /$$$$$$$/
26
- |__/ |__/|__/ \__/ \______/ |__/ |__/|__/ |__/ \______/ | $$____/ |_______/
27
- | $$
28
- | $$
29
- |__/
19
+ /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$ /$$ /$$
20
+ /$$__ $$| $$ | $$ /$$__ $$ | $$__ $$ | $$ | $$
21
+ | $$ \__/| $$ /$$$$$$ /$$ /$$ /$$$$$$$| $$ \ $$ /$$$$$$ /$$$$$$$ | $$ \ $$ /$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$ | $$ /$$ /$$
22
+ | $$ | $$ /$$__ $$| $$ | $$ /$$__ $$| $$ | $$ /$$__ $$ /$$_____/ | $$$$$$$/ | $$ | $$| $$__ $$ | $$__ $$ /$$__ $$ /$$__ $$| $$ | $$ | $$
23
+ | $$ | $$| $$ \ $$| $$ | $$| $$ | $$| $$ | $$| $$ \ $$| $$$$$$ | $$__ $$ | $$ | $$| $$ \ $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$ | $$ | $$
24
+ | $$ $$| $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ \____ $$ | $$ \ $$ | $$ | $$| $$ | $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$ | $$
25
+ | $$$$$$/| $$| $$$$$$/| $$$$$$/| $$$$$$$| $$$$$$/| $$$$$$$/ /$$$$$$$/ | $$ | $$ | $$$$$$/| $$ | $$ | $$$$$$$/| $$$$$$/| $$$$$$/| $$ | $$$$$$/
26
+ \______/ |__/ \______/ \______/ \_______/ \______/ | $$____/ |_______/ |__/ |__/ \______/ |__/ |__/ |_______/ \______/ \______/ |__/ \______/
27
+ | $$
28
+ | $$
29
+ |__/
30
30
  [/]
31
- [bold bright_blue]AWS FinOps Dashboard CLI (v{__version__})[/]
31
+ [bold bright_blue]CloudOps Runbooks FinOps Platform (v{__version__})[/]
32
32
  """
33
33
  console.print(banner)
34
34
 
35
35
 
36
36
  def check_latest_version() -> None:
37
- """Check for the latest version of the AWS FinOps Dashboard (CLI)."""
37
+ """Check for the latest version of the CloudOps Runbooks package."""
38
38
  try:
39
- response = requests.get("https://pypi.org/pypi/aws-finops-dashboard/json", timeout=3)
39
+ response = requests.get("https://pypi.org/pypi/runbooks/json", timeout=3)
40
40
  latest = response.json()["info"]["version"]
41
41
  if version.parse(latest) > version.parse(__version__):
42
- console.print(f"[bold red]A new version of AWS FinOps Dashboard is available: {latest}[/]")
42
+ console.print(f"[bold red]A new version of CloudOps Runbooks is available: {latest}[/]")
43
43
  console.print(
44
- "[bold bright_yellow]Please update using:\npipx upgrade aws-finops-dashboard\nor\npip install --upgrade aws-finops-dashboard\n[/]"
44
+ "[bold bright_yellow]Please update using:\npip install --upgrade runbooks\nor\nuv add runbooks@latest\n[/]"
45
45
  )
46
46
  except Exception:
47
47
  pass
@@ -54,7 +54,9 @@ def main() -> int:
54
54
  from runbooks.finops.dashboard_runner import run_dashboard
55
55
 
56
56
  # Create the parser instance to be accessible for get_default
57
- parser = argparse.ArgumentParser(description="AWS FinOps Dashboard CLI")
57
+ parser = argparse.ArgumentParser(
58
+ description="CloudOps Runbooks FinOps Platform - Enterprise Multi-Account Cost Optimization"
59
+ )
58
60
 
59
61
  parser.add_argument(
60
62
  "--config-file",
@@ -94,10 +96,10 @@ def main() -> int:
94
96
  "--report-type",
95
97
  "-y",
96
98
  nargs="+",
97
- choices=["csv", "json", "pdf"],
98
- help="Specify one or more report types: csv and/or json and/or pdf (space-separated)",
99
+ choices=["csv", "json", "pdf", "markdown"],
100
+ help="Specify one or more report types: csv and/or json and/or pdf and/or markdown (space-separated)",
99
101
  type=str,
100
- default=["csv"],
102
+ default=["markdown"],
101
103
  )
102
104
  parser.add_argument(
103
105
  "--dir",
@@ -145,6 +147,44 @@ def main() -> int:
145
147
  help="Run PDCA in continuous mode (until manually stopped)",
146
148
  )
147
149
 
150
+ # Enhanced Dashboard Configuration Parameters
151
+ parser.add_argument(
152
+ "--mode",
153
+ choices=["single_account", "multi_account"],
154
+ help="Explicit dashboard mode selection (overrides auto-detection)",
155
+ type=str,
156
+ )
157
+ parser.add_argument(
158
+ "--top-services",
159
+ help="Number of top services to display in single-account mode (default: 10)",
160
+ type=int,
161
+ default=10,
162
+ )
163
+ parser.add_argument(
164
+ "--top-accounts",
165
+ help="Number of top accounts to display in multi-account mode (default: 5)",
166
+ type=int,
167
+ default=5,
168
+ )
169
+ parser.add_argument(
170
+ "--services-per-account",
171
+ help="Number of services to show per account in multi-account mode (default: 3)",
172
+ type=int,
173
+ default=3,
174
+ )
175
+ parser.add_argument(
176
+ "--format",
177
+ choices=["table", "json", "csv", "markdown"],
178
+ help="Output format for dashboard display (default: markdown)",
179
+ type=str,
180
+ default="markdown",
181
+ )
182
+ parser.add_argument(
183
+ "--no-enhanced-routing",
184
+ action="store_true",
185
+ help="Disable enhanced service-focused routing (use legacy account-per-row layout)",
186
+ )
187
+
148
188
  args = parser.parse_args()
149
189
 
150
190
  config_data: Optional[Dict[str, Any]] = None
@@ -162,39 +202,40 @@ def main() -> int:
162
202
  # Handle PDCA mode
163
203
  if args.pdca or args.pdca_continuous:
164
204
  import asyncio
205
+
165
206
  from runbooks.finops.pdca_engine import AutonomousPDCAEngine, PDCAThresholds
166
-
207
+
167
208
  console.print("[bold bright_cyan]πŸ€– Launching Autonomous PDCA Engine...[/]")
168
-
209
+
169
210
  # Configure PDCA thresholds
170
211
  thresholds = PDCAThresholds(
171
212
  max_risk_score=25,
172
213
  max_cost_increase=10.0,
173
214
  max_untagged_resources=50,
174
215
  max_unused_eips=5,
175
- max_budget_overruns=1
216
+ max_budget_overruns=1,
176
217
  )
177
-
218
+
178
219
  # Initialize PDCA engine
179
220
  artifacts_dir = args.dir or "artifacts"
180
221
  engine = AutonomousPDCAEngine(thresholds=thresholds, artifacts_dir=artifacts_dir)
181
-
222
+
182
223
  try:
183
224
  # Determine execution mode
184
225
  continuous_mode = args.pdca_continuous
185
226
  max_cycles = 0 if continuous_mode else args.pdca_cycles
186
-
227
+
187
228
  # Run PDCA cycles
188
229
  metrics_history = asyncio.run(engine.run_autonomous_cycles(max_cycles, continuous_mode))
189
-
230
+
190
231
  # Generate summary report
191
232
  engine.generate_cycle_summary_report()
192
-
233
+
193
234
  console.print(f"\n[bold bright_green]πŸŽ‰ PDCA Engine completed successfully![/]")
194
235
  console.print(f"[cyan]Generated {len(metrics_history)} cycle reports in: {engine.pdca_dir}[/]")
195
-
236
+
196
237
  return 0
197
-
238
+
198
239
  except KeyboardInterrupt:
199
240
  console.print(f"\n[yellow]⏸️ PDCA Engine stopped by user[/]")
200
241
  if engine.cycle_history:
@@ -204,8 +245,24 @@ def main() -> int:
204
245
  console.print(f"\n[red]❌ PDCA Engine failed: {str(e)}[/]")
205
246
  return 1
206
247
 
207
- # Default dashboard mode
208
- result = run_dashboard(args)
248
+ # Enhanced routing is now the default (service-per-row layout)
249
+ # Maintain backward compatibility with explicit --no-enhanced-routing flag
250
+ use_enhanced_routing = not getattr(args, "no_enhanced_routing", False)
251
+
252
+ if use_enhanced_routing:
253
+ try:
254
+ from runbooks.finops.dashboard_router import route_finops_request
255
+
256
+ console.print("[bold bright_cyan]πŸš€ Using Enhanced Service-Focused Dashboard[/]")
257
+ result = route_finops_request(args)
258
+ except Exception as e:
259
+ console.print(f"[yellow]⚠️ Enhanced routing failed ({str(e)[:50]}), falling back to legacy mode[/]")
260
+ result = run_dashboard(args)
261
+ else:
262
+ # Legacy dashboard mode (backward compatibility)
263
+ console.print("[dim]Using legacy dashboard mode[/]")
264
+ result = run_dashboard(args)
265
+
209
266
  return 0 if result == 0 else 1
210
267
 
211
268