runbooks 1.1.4__py3-none-any.whl → 1.1.5__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 (228) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +138 -35
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +11 -0
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +63 -74
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +201 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/cloud_foundations_integration.py +144 -149
  113. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  114. runbooks/inventory/collectors/aws_networking.py +109 -99
  115. runbooks/inventory/collectors/base.py +4 -0
  116. runbooks/inventory/core/collector.py +495 -313
  117. runbooks/inventory/drift_detection_cli.py +69 -96
  118. runbooks/inventory/inventory_mcp_cli.py +48 -46
  119. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  120. runbooks/inventory/mcp_inventory_validator.py +549 -465
  121. runbooks/inventory/mcp_vpc_validator.py +359 -442
  122. runbooks/inventory/organizations_discovery.py +55 -51
  123. runbooks/inventory/rich_inventory_display.py +33 -32
  124. runbooks/inventory/unified_validation_engine.py +278 -251
  125. runbooks/inventory/vpc_analyzer.py +732 -695
  126. runbooks/inventory/vpc_architecture_validator.py +293 -348
  127. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  128. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  129. runbooks/main.py +49 -34
  130. runbooks/main_final.py +91 -60
  131. runbooks/main_minimal.py +22 -10
  132. runbooks/main_optimized.py +131 -100
  133. runbooks/main_ultra_minimal.py +7 -2
  134. runbooks/mcp/__init__.py +36 -0
  135. runbooks/mcp/integration.py +679 -0
  136. runbooks/monitoring/performance_monitor.py +9 -4
  137. runbooks/operate/dynamodb_operations.py +3 -1
  138. runbooks/operate/ec2_operations.py +145 -137
  139. runbooks/operate/iam_operations.py +146 -152
  140. runbooks/operate/networking_cost_heatmap.py +29 -8
  141. runbooks/operate/rds_operations.py +223 -254
  142. runbooks/operate/s3_operations.py +107 -118
  143. runbooks/operate/vpc_operations.py +646 -616
  144. runbooks/remediation/base.py +1 -1
  145. runbooks/remediation/commons.py +10 -7
  146. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  147. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  148. runbooks/remediation/multi_account.py +24 -21
  149. runbooks/remediation/rds_snapshot_list.py +86 -60
  150. runbooks/remediation/remediation_cli.py +92 -146
  151. runbooks/remediation/universal_account_discovery.py +83 -79
  152. runbooks/remediation/workspaces_list.py +46 -41
  153. runbooks/security/__init__.py +19 -0
  154. runbooks/security/assessment_runner.py +1150 -0
  155. runbooks/security/baseline_checker.py +812 -0
  156. runbooks/security/cloudops_automation_security_validator.py +509 -535
  157. runbooks/security/compliance_automation_engine.py +17 -17
  158. runbooks/security/config/__init__.py +2 -2
  159. runbooks/security/config/compliance_config.py +50 -50
  160. runbooks/security/config_template_generator.py +63 -76
  161. runbooks/security/enterprise_security_framework.py +1 -1
  162. runbooks/security/executive_security_dashboard.py +519 -508
  163. runbooks/security/multi_account_security_controls.py +959 -1210
  164. runbooks/security/real_time_security_monitor.py +422 -444
  165. runbooks/security/security_baseline_tester.py +1 -1
  166. runbooks/security/security_cli.py +143 -112
  167. runbooks/security/test_2way_validation.py +439 -0
  168. runbooks/security/two_way_validation_framework.py +852 -0
  169. runbooks/sre/production_monitoring_framework.py +167 -177
  170. runbooks/tdd/__init__.py +15 -0
  171. runbooks/tdd/cli.py +1071 -0
  172. runbooks/utils/__init__.py +14 -17
  173. runbooks/utils/logger.py +7 -2
  174. runbooks/utils/version_validator.py +50 -47
  175. runbooks/validation/__init__.py +6 -6
  176. runbooks/validation/cli.py +9 -3
  177. runbooks/validation/comprehensive_2way_validator.py +745 -704
  178. runbooks/validation/mcp_validator.py +906 -228
  179. runbooks/validation/terraform_citations_validator.py +104 -115
  180. runbooks/validation/terraform_drift_detector.py +447 -451
  181. runbooks/vpc/README.md +617 -0
  182. runbooks/vpc/__init__.py +8 -1
  183. runbooks/vpc/analyzer.py +577 -0
  184. runbooks/vpc/cleanup_wrapper.py +476 -413
  185. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  186. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  187. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  188. runbooks/vpc/config.py +92 -97
  189. runbooks/vpc/cost_engine.py +411 -148
  190. runbooks/vpc/cost_explorer_integration.py +553 -0
  191. runbooks/vpc/cross_account_session.py +101 -106
  192. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  193. runbooks/vpc/eni_gate_validator.py +961 -0
  194. runbooks/vpc/heatmap_engine.py +185 -160
  195. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  196. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  197. runbooks/vpc/networking_wrapper.py +15 -8
  198. runbooks/vpc/pdca_remediation_planner.py +528 -0
  199. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  200. runbooks/vpc/runbooks_adapter.py +1167 -241
  201. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  202. runbooks/vpc/test_data_loader.py +358 -0
  203. runbooks/vpc/tests/conftest.py +314 -4
  204. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  205. runbooks/vpc/tests/test_cost_engine.py +0 -2
  206. runbooks/vpc/topology_generator.py +326 -0
  207. runbooks/vpc/unified_scenarios.py +1297 -1124
  208. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  209. runbooks-1.1.5.dist-info/METADATA +328 -0
  210. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
  211. runbooks/finops/README.md +0 -414
  212. runbooks/finops/accuracy_cross_validator.py +0 -647
  213. runbooks/finops/business_cases.py +0 -950
  214. runbooks/finops/dashboard_router.py +0 -922
  215. runbooks/finops/ebs_optimizer.py +0 -973
  216. runbooks/finops/embedded_mcp_validator.py +0 -1629
  217. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  218. runbooks/finops/finops_dashboard.py +0 -584
  219. runbooks/finops/finops_scenarios.py +0 -1218
  220. runbooks/finops/legacy_migration.py +0 -730
  221. runbooks/finops/multi_dashboard.py +0 -1519
  222. runbooks/finops/single_dashboard.py +0 -1113
  223. runbooks/finops/unlimited_scenarios.py +0 -393
  224. runbooks-1.1.4.dist-info/METADATA +0 -800
  225. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  226. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  227. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  228. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -35,10 +35,10 @@ styles = getSampleStyleSheet()
35
35
  def format_currency(amount) -> str:
36
36
  """
37
37
  Format currency for business display in notebooks.
38
-
38
+
39
39
  Args:
40
40
  amount: Numeric amount to format
41
-
41
+
42
42
  Returns:
43
43
  Formatted currency string
44
44
  """
@@ -56,162 +56,165 @@ def format_currency(amount) -> str:
56
56
  def create_business_summary_table(scenarios: Dict[str, Any], rich_console: Optional[Console] = None) -> str:
57
57
  """
58
58
  Create a business-friendly summary table for notebook display.
59
-
59
+
60
60
  Args:
61
61
  scenarios: Dictionary of business scenarios
62
62
  rich_console: Optional Rich console for enhanced formatting
63
-
63
+
64
64
  Returns:
65
65
  Formatted table string suitable for notebook display
66
66
  """
67
67
  output = []
68
-
68
+
69
69
  # Header
70
70
  output.append("Business Case Summary")
71
71
  output.append("=" * 60)
72
72
  output.append(f"{'Scenario':<25} | {'Annual Savings':<15} | {'Status':<15}")
73
73
  output.append("-" * 60)
74
-
74
+
75
75
  # Process scenarios
76
76
  for key, scenario in scenarios.items():
77
- if key == 'metadata':
77
+ if key == "metadata":
78
78
  continue
79
-
79
+
80
80
  # Extract scenario info
81
- title = scenario.get('title', scenario.get('description', key))[:24]
82
-
81
+ title = scenario.get("title", scenario.get("description", key))[:24]
82
+
83
83
  # Format savings
84
- if 'actual_savings' in scenario:
85
- savings = format_currency(scenario['actual_savings'])
86
- elif 'savings_range' in scenario:
87
- range_data = scenario['savings_range']
84
+ if "actual_savings" in scenario:
85
+ savings = format_currency(scenario["actual_savings"])
86
+ elif "savings_range" in scenario:
87
+ range_data = scenario["savings_range"]
88
88
  savings = f"{format_currency(range_data['min'])}-{format_currency(range_data['max'])}"
89
89
  else:
90
90
  savings = "Under investigation"
91
-
91
+
92
92
  # Status
93
- status = scenario.get('status', 'Analysis ready')[:14]
94
-
93
+ status = scenario.get("status", "Analysis ready")[:14]
94
+
95
95
  output.append(f"{title:<25} | {savings:<15} | {status:<15}")
96
-
96
+
97
97
  # Footer
98
- if 'metadata' in scenarios:
98
+ if "metadata" in scenarios:
99
99
  output.append("-" * 60)
100
100
  output.append(f"Generated: {scenarios['metadata'].get('generated_at', 'Unknown')}")
101
101
  output.append(f"Source: {scenarios['metadata'].get('data_source', 'Unknown')}")
102
-
102
+
103
103
  return "\n".join(output)
104
104
 
105
105
 
106
106
  def export_scenarios_to_notebook_html(scenarios: Dict[str, Any], title: str = "FinOps Analysis") -> str:
107
107
  """
108
108
  Export scenarios as HTML suitable for Jupyter notebook display.
109
-
109
+
110
110
  Args:
111
111
  scenarios: Dictionary of business scenarios
112
112
  title: HTML document title
113
-
113
+
114
114
  Returns:
115
115
  HTML string for notebook display
116
116
  """
117
117
  html = []
118
-
118
+
119
119
  # HTML header
120
120
  html.append(f"""
121
121
  <div style="font-family: Arial, sans-serif; margin: 20px;">
122
122
  <h2 style="color: #2c5aa0;">{title}</h2>
123
123
  """)
124
-
124
+
125
125
  # Process scenarios
126
126
  for key, scenario in scenarios.items():
127
- if key == 'metadata':
127
+ if key == "metadata":
128
128
  continue
129
-
130
- title_text = scenario.get('title', scenario.get('description', key))
131
-
129
+
130
+ title_text = scenario.get("title", scenario.get("description", key))
131
+
132
132
  html.append(f'<div style="border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px;">')
133
133
  html.append(f'<h3 style="color: #1f4788; margin-top: 0;">{title_text}</h3>')
134
-
134
+
135
135
  # Savings information
136
- if 'actual_savings' in scenario:
137
- savings = format_currency(scenario['actual_savings'])
138
- html.append(f'<p><strong>💰 Annual Savings:</strong> {savings}</p>')
139
- elif 'savings_range' in scenario:
140
- range_data = scenario['savings_range']
141
- min_savings = format_currency(range_data['min'])
142
- max_savings = format_currency(range_data['max'])
143
- html.append(f'<p><strong>💰 Annual Savings:</strong> {min_savings} - {max_savings}</p>')
136
+ if "actual_savings" in scenario:
137
+ savings = format_currency(scenario["actual_savings"])
138
+ html.append(f"<p><strong>💰 Annual Savings:</strong> {savings}</p>")
139
+ elif "savings_range" in scenario:
140
+ range_data = scenario["savings_range"]
141
+ min_savings = format_currency(range_data["min"])
142
+ max_savings = format_currency(range_data["max"])
143
+ html.append(f"<p><strong>💰 Annual Savings:</strong> {min_savings} - {max_savings}</p>")
144
144
  else:
145
- html.append('<p><strong>💰 Annual Savings:</strong> Under investigation</p>')
146
-
145
+ html.append("<p><strong>💰 Annual Savings:</strong> Under investigation</p>")
146
+
147
147
  # Implementation details
148
- if 'implementation_time' in scenario:
149
- html.append(f'<p><strong>⏱️ Implementation:</strong> {scenario["implementation_time"]}</p>')
150
-
151
- if 'risk_level' in scenario:
152
- html.append(f'<p><strong>🛡️ Risk Level:</strong> {scenario["risk_level"]}</p>')
153
-
154
- html.append('</div>')
155
-
148
+ if "implementation_time" in scenario:
149
+ html.append(f"<p><strong>⏱️ Implementation:</strong> {scenario['implementation_time']}</p>")
150
+
151
+ if "risk_level" in scenario:
152
+ html.append(f"<p><strong>🛡️ Risk Level:</strong> {scenario['risk_level']}</p>")
153
+
154
+ html.append("</div>")
155
+
156
156
  # Metadata footer
157
- if 'metadata' in scenarios:
158
- metadata = scenarios['metadata']
157
+ if "metadata" in scenarios:
158
+ metadata = scenarios["metadata"]
159
159
  html.append('<div style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;">')
160
- html.append(f'<small><strong>Data Source:</strong> {metadata.get("data_source", "Unknown")}<br>')
161
- html.append(f'<strong>Generated:</strong> {metadata.get("generated_at", "Unknown")}</small>')
162
- html.append('</div>')
163
-
164
- html.append('</div>')
165
-
166
- return ''.join(html)
160
+ html.append(f"<small><strong>Data Source:</strong> {metadata.get('data_source', 'Unknown')}<br>")
161
+ html.append(f"<strong>Generated:</strong> {metadata.get('generated_at', 'Unknown')}</small>")
162
+ html.append("</div>")
163
+
164
+ html.append("</div>")
165
+
166
+ return "".join(html)
167
167
 
168
168
 
169
169
  def create_roi_analysis_table(business_cases: List[Dict[str, Any]]) -> str:
170
170
  """
171
171
  Create ROI analysis table for business decision making.
172
-
172
+
173
173
  Args:
174
174
  business_cases: List of business case dictionaries
175
-
175
+
176
176
  Returns:
177
177
  Formatted ROI analysis table
178
178
  """
179
179
  output = []
180
-
180
+
181
181
  # Header
182
182
  output.append("ROI Analysis Summary")
183
183
  output.append("=" * 80)
184
184
  output.append(f"{'Business Case':<25} | {'Annual Savings':<12} | {'ROI %':<8} | {'Risk':<8} | {'Payback':<10}")
185
185
  output.append("-" * 80)
186
-
186
+
187
187
  total_savings = 0
188
-
188
+
189
189
  # Process business cases
190
190
  for case in business_cases:
191
- title = case.get('title', 'Unknown Case')[:24]
192
-
191
+ title = case.get("title", "Unknown Case")[:24]
192
+
193
193
  # Extract financial metrics
194
- savings = case.get('annual_savings', case.get('actual_savings', 0))
195
- roi = case.get('roi_percentage', 0)
196
- risk = case.get('risk_level', 'Medium')[:7]
197
- payback = case.get('payback_months', 0)
198
-
194
+ savings = case.get("annual_savings", case.get("actual_savings", 0))
195
+ roi = case.get("roi_percentage", 0)
196
+ risk = case.get("risk_level", "Medium")[:7]
197
+ payback = case.get("payback_months", 0)
198
+
199
199
  # Format values
200
200
  savings_str = format_currency(savings)[:11]
201
201
  roi_str = f"{roi:.0f}%" if roi < 9999 else ">999%"
202
202
  payback_str = f"{payback:.1f}mo" if payback > 0 else "Immediate"
203
-
203
+
204
204
  output.append(f"{title:<25} | {savings_str:<12} | {roi_str:<8} | {risk:<8} | {payback_str:<10}")
205
-
205
+
206
206
  if isinstance(savings, (int, float)):
207
207
  total_savings += savings
208
-
208
+
209
209
  # Summary
210
210
  output.append("-" * 80)
211
- output.append(f"{'TOTAL PORTFOLIO':<25} | {format_currency(total_savings):<12} | {'N/A':<8} | {'Mixed':<8} | {'Varies':<10}")
212
-
211
+ output.append(
212
+ f"{'TOTAL PORTFOLIO':<25} | {format_currency(total_savings):<12} | {'N/A':<8} | {'Mixed':<8} | {'Varies':<10}"
213
+ )
214
+
213
215
  return "\n".join(output)
214
216
 
217
+
215
218
  # Custom style for the footer
216
219
  audit_footer_style = ParagraphStyle(
217
220
  name="AuditFooter",
@@ -286,14 +289,14 @@ def export_audit_report_to_pdf(
286
289
  untagged_display = ""
287
290
  if row.get("untagged_count", 0) > 0:
288
291
  # Format like reference: "EC2: us-east-1: i-1234567890"
289
- account_id = row.get('account_id', 'unknown')
290
- if account_id and account_id != 'unknown':
292
+ account_id = row.get("account_id", "unknown")
293
+ if account_id and account_id != "unknown":
291
294
  untagged_display = f"EC2:\nus-east-1:\ni-{account_id[:10]}"
292
295
  else:
293
296
  untagged_display = f"EC2:\nus-east-1:\ni-unavailable"
294
297
  if row.get("untagged_count", 0) > 1:
295
- account_id = row.get('account_id', 'unknown')
296
- if account_id and account_id != 'unknown':
298
+ account_id = row.get("account_id", "unknown")
299
+ if account_id and account_id != "unknown":
297
300
  untagged_display += f"\n\nRDS:\nus-west-2:\ndb-{account_id[:10]}"
298
301
  else:
299
302
  untagged_display += f"\n\nRDS:\nus-west-2:\ndb-unavailable"
@@ -818,7 +821,9 @@ def export_cost_dashboard_to_markdown_legacy(
818
821
 
819
822
  # Footer
820
823
  markdown_lines.append("---")
821
- markdown_lines.append("*Generated by CloudOps Runbooks FinOps Dashboard - Enterprise Cost Management Platform*")
824
+ markdown_lines.append(
825
+ "*Generated by CloudOps & FinOps Runbooks Dashboard - Enterprise Cost Management Platform*"
826
+ )
822
827
  markdown_lines.append("")
823
828
  markdown_lines.append(
824
829
  "For more information, visit: [CloudOps Documentation](https://github.com/1xOps/CloudOps-Runbooks)"
@@ -949,14 +954,36 @@ def export_cost_dashboard_to_pdf(
949
954
  budget_limit = current_cost * 1.3 # 30% buffer
950
955
  forecast = current_cost * 1.05 # 5% forecast increase
951
956
 
952
- budget_name = f"{'DevOps' if 'dev' in profile_display else 'Production' if 'prod' in profile_display else 'QA'} Budget"
957
+ # Determine budget type for display
958
+ budget_type = "DevOps" if "dev" in profile_display else "Prod" if "prod" in profile_display else "QA"
959
+
960
+ # Calculate utilization for status determination
961
+ utilization = (current_cost / budget_limit) * 100 if budget_limit > 0 else 0
962
+
963
+ # Status icon based on utilization
964
+ if utilization >= 100:
965
+ status_icon = "🚨" # Over budget
966
+ elif utilization >= 85:
967
+ status_icon = "⚠️" # Near limit
968
+ elif utilization >= 70:
969
+ status_icon = "🟡" # Moderate usage
970
+ else:
971
+ status_icon = "✅" # Under budget
972
+
973
+ # Concise budget display with icons
974
+ budget_status = (
975
+ f"{status_icon} {budget_type}\n💰 ${current_cost:.0f}/${budget_limit:.0f} ({utilization:.0f}%)"
976
+ )
953
977
 
954
- budget_status = f"{budget_name}:\n\nLimit: ${budget_limit:.2f}\nActual: ${current_cost:.2f}\nForecast: ${forecast:.2f}"
978
+ # Add forecast only if significantly different
979
+ if abs(forecast - current_cost) > (current_cost * 0.05): # 5% threshold
980
+ trend_icon = "📈" if forecast > current_cost else "📉"
981
+ budget_status += f"\n{trend_icon} Est: ${forecast:.0f}"
955
982
 
956
983
  if len(data) > 2 and i == 2: # Third row - show "No budgets found" like reference
957
- budget_status = "No budgets found.\nCreate a budget in the console."
984
+ budget_status = "ℹ️ No budgets found\n💡 Create in console"
958
985
  else:
959
- budget_status = "No budgets found.\nCreate a budget in the console."
986
+ budget_status = "ℹ️ No budgets found\n💡 Create in console"
960
987
 
961
988
  # Format EC2 instances like reference
962
989
  ec2_summary = ""
@@ -1108,192 +1135,9 @@ def load_config_file(file_path: str) -> Optional[Dict[str, Any]]:
1108
1135
  return None
1109
1136
 
1110
1137
 
1111
- def generate_pdca_improvement_report(
1112
- pdca_metrics: List[Dict[str, Any]], file_name: str = "pdca_improvement", path: Optional[str] = None
1113
- ) -> Optional[str]:
1114
- """
1115
- Generate PDCA (Plan-Do-Check-Act) continuous improvement report.
1116
-
1117
- :param pdca_metrics: List of PDCA metrics for each profile
1118
- :param file_name: The base name of the output file
1119
- :param path: Optional directory where the file will be saved
1120
- :return: Full path of the generated report or None on error
1121
- """
1122
- try:
1123
- timestamp = datetime.now().strftime("%Y%m%d_%H%M")
1124
- base_filename = f"{file_name}_pdca_report_{timestamp}.json"
1125
-
1126
- if path:
1127
- os.makedirs(path, exist_ok=True)
1128
- output_filename = os.path.join(path, base_filename)
1129
- else:
1130
- output_filename = base_filename
1131
-
1132
- # Calculate aggregate metrics
1133
- total_risk = sum(m["risk_score"] for m in pdca_metrics)
1134
- avg_risk = total_risk / len(pdca_metrics) if pdca_metrics else 0
1135
-
1136
- high_risk_accounts = [m for m in pdca_metrics if m["risk_score"] > 25]
1137
- medium_risk_accounts = [m for m in pdca_metrics if 10 < m["risk_score"] <= 25]
1138
- low_risk_accounts = [m for m in pdca_metrics if m["risk_score"] <= 10]
1139
-
1140
- total_untagged = sum(m["untagged_count"] for m in pdca_metrics)
1141
- total_stopped = sum(m["stopped_count"] for m in pdca_metrics)
1142
- total_unused_volumes = sum(m["unused_volumes_count"] for m in pdca_metrics)
1143
- total_unused_eips = sum(m["unused_eips_count"] for m in pdca_metrics)
1144
- total_budget_overruns = sum(m["budget_overruns"] for m in pdca_metrics)
1145
-
1146
- # Generate improvement recommendations
1147
- recommendations = []
1148
-
1149
- # PLAN phase recommendations
1150
- if total_untagged > 50:
1151
- recommendations.append(
1152
- {
1153
- "phase": "PLAN",
1154
- "priority": "HIGH",
1155
- "category": "Compliance",
1156
- "issue": f"Found {total_untagged} untagged resources across all accounts",
1157
- "action": "Implement mandatory tagging strategy using AWS Config rules",
1158
- "expected_outcome": "100% resource compliance within 30 days",
1159
- "owner": "Cloud Governance Team",
1160
- }
1161
- )
1162
-
1163
- if total_unused_eips > 5:
1164
- recommendations.append(
1165
- {
1166
- "phase": "PLAN",
1167
- "priority": "MEDIUM",
1168
- "category": "Cost Optimization",
1169
- "issue": f"Found {total_unused_eips} unused Elastic IPs",
1170
- "action": "Schedule monthly EIP cleanup automation",
1171
- "expected_outcome": f"Save ~${total_unused_eips * 3.65:.2f}/month",
1172
- "owner": "FinOps Team",
1173
- }
1174
- )
1175
-
1176
- # DO phase recommendations
1177
- if high_risk_accounts:
1178
- recommendations.append(
1179
- {
1180
- "phase": "DO",
1181
- "priority": "CRITICAL",
1182
- "category": "Risk Management",
1183
- "issue": f"{len(high_risk_accounts)} accounts have critical risk scores",
1184
- "action": "Execute immediate remediation on high-risk accounts",
1185
- "expected_outcome": "Reduce risk scores by 70% within 2 weeks",
1186
- "owner": "Security Team",
1187
- }
1188
- )
1189
-
1190
- # CHECK phase recommendations
1191
- if avg_risk > 15:
1192
- recommendations.append(
1193
- {
1194
- "phase": "CHECK",
1195
- "priority": "HIGH",
1196
- "category": "Monitoring",
1197
- "issue": f"Average risk score ({avg_risk:.1f}) exceeds threshold",
1198
- "action": "Implement automated risk scoring dashboard",
1199
- "expected_outcome": "Real-time risk visibility and alerting",
1200
- "owner": "DevOps Team",
1201
- }
1202
- )
1203
-
1204
- # ACT phase recommendations
1205
- recommendations.append(
1206
- {
1207
- "phase": "ACT",
1208
- "priority": "MEDIUM",
1209
- "category": "Process Improvement",
1210
- "issue": "Need continuous improvement framework",
1211
- "action": "Establish monthly PDCA review cycles",
1212
- "expected_outcome": "25% reduction in average risk score per quarter",
1213
- "owner": "Cloud Center of Excellence",
1214
- }
1215
- )
1216
-
1217
- # Create comprehensive PDCA report
1218
- pdca_report = {
1219
- "report_metadata": {
1220
- "generated_at": datetime.now().isoformat(),
1221
- "report_type": "PDCA Continuous Improvement Analysis",
1222
- "accounts_analyzed": len(pdca_metrics),
1223
- "framework_version": "v1.0",
1224
- },
1225
- "executive_summary": {
1226
- "overall_risk_score": total_risk,
1227
- "average_risk_score": round(avg_risk, 2),
1228
- "risk_distribution": {
1229
- "critical_accounts": len(high_risk_accounts),
1230
- "high_risk_accounts": len(medium_risk_accounts),
1231
- "low_risk_accounts": len(low_risk_accounts),
1232
- },
1233
- "key_findings": {
1234
- "untagged_resources": total_untagged,
1235
- "stopped_instances": total_stopped,
1236
- "unused_volumes": total_unused_volumes,
1237
- "unused_elastic_ips": total_unused_eips,
1238
- "budget_overruns": total_budget_overruns,
1239
- },
1240
- },
1241
- "pdca_analysis": {
1242
- "plan_phase": {
1243
- "description": "Strategic planning based on current state analysis",
1244
- "metrics_collected": len(pdca_metrics),
1245
- "baseline_established": True,
1246
- },
1247
- "do_phase": {
1248
- "description": "Implementation of audit data collection",
1249
- "data_sources": ["EC2", "RDS", "Lambda", "ELBv2", "Budgets"],
1250
- "regions_scanned": "All accessible regions",
1251
- },
1252
- "check_phase": {
1253
- "description": "Analysis of collected audit data",
1254
- "risk_assessment_completed": True,
1255
- "trends_identified": True,
1256
- },
1257
- "act_phase": {
1258
- "description": "Actionable recommendations for improvement",
1259
- "recommendations_generated": len(recommendations),
1260
- "prioritization_completed": True,
1261
- },
1262
- },
1263
- "detailed_metrics": pdca_metrics,
1264
- "improvement_recommendations": recommendations,
1265
- "next_steps": {
1266
- "immediate_actions": [
1267
- "Review high-risk accounts within 48 hours",
1268
- "Implement automated tagging for untagged resources",
1269
- "Schedule EIP cleanup automation",
1270
- ],
1271
- "medium_term_goals": [
1272
- "Establish monthly PDCA review cycle",
1273
- "Implement risk scoring dashboard",
1274
- "Create automated remediation workflows",
1275
- ],
1276
- "long_term_objectives": [
1277
- "Achieve average risk score below 5",
1278
- "Maintain 100% resource compliance",
1279
- "Reduce cloud waste by 25%",
1280
- ],
1281
- },
1282
- }
1283
-
1284
- # Export to JSON
1285
- with open(output_filename, "w", encoding="utf-8") as jsonfile:
1286
- json.dump(pdca_report, jsonfile, indent=4, default=str)
1287
-
1288
- return os.path.abspath(output_filename)
1289
-
1290
- except Exception as e:
1291
- console.print(f"[bold red]Error generating PDCA improvement report: {str(e)}[/]")
1292
- return None
1293
-
1294
-
1295
- def export_scenario_results(results: Dict[str, Any], scenario_name: str,
1296
- report_types: List[str], output_dir: Optional[str] = None) -> bool:
1138
+ def export_scenario_results(
1139
+ results: Dict[str, Any], scenario_name: str, report_types: List[str], output_dir: Optional[str] = None
1140
+ ) -> bool:
1297
1141
  """
1298
1142
  Export business scenario results in specified formats.
1299
1143
 
@@ -1307,7 +1151,7 @@ def export_scenario_results(results: Dict[str, Any], scenario_name: str,
1307
1151
  True if all exports succeeded
1308
1152
  """
1309
1153
  try:
1310
- from runbooks.common.rich_utils import print_success, print_error, print_info
1154
+ from runbooks.common.rich_utils import print_error, print_info, print_success
1311
1155
 
1312
1156
  output_dir = output_dir or "."
1313
1157
  base_filename = f"finops-scenario-{scenario_name}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
@@ -1317,64 +1161,62 @@ def export_scenario_results(results: Dict[str, Any], scenario_name: str,
1317
1161
 
1318
1162
  for report_type in report_types:
1319
1163
  try:
1320
- if report_type == 'json':
1164
+ if report_type == "json":
1321
1165
  output_path = os.path.join(output_dir, f"{base_filename}.json")
1322
- with open(output_path, 'w', encoding='utf-8') as f:
1166
+ with open(output_path, "w", encoding="utf-8") as f:
1323
1167
  json.dump(results, f, indent=2, default=str)
1324
1168
  print_info(f"📄 JSON export: {output_path}")
1325
1169
 
1326
- elif report_type == 'csv':
1170
+ elif report_type == "csv":
1327
1171
  output_path = os.path.join(output_dir, f"{base_filename}.csv")
1328
1172
  # Extract key metrics for CSV export
1329
- if 'business_impact' in results:
1330
- business_data = results['business_impact']
1331
- with open(output_path, 'w', newline='', encoding='utf-8') as f:
1173
+ if "business_impact" in results:
1174
+ business_data = results["business_impact"]
1175
+ with open(output_path, "w", newline="", encoding="utf-8") as f:
1332
1176
  writer = csv.DictWriter(f, fieldnames=business_data.keys())
1333
1177
  writer.writeheader()
1334
1178
  writer.writerow(business_data)
1335
1179
  print_info(f"📊 CSV export: {output_path}")
1336
1180
 
1337
- elif report_type == 'pdf':
1181
+ elif report_type == "pdf":
1338
1182
  output_path = os.path.join(output_dir, f"{base_filename}.pdf")
1339
1183
  # Create basic PDF with scenario results
1340
1184
  doc = SimpleDocTemplate(output_path, pagesize=letter)
1341
1185
  story = []
1342
1186
 
1343
1187
  # Title
1344
- title = Paragraph(f"FinOps Business Scenario: {scenario_name.title()}",
1345
- styles['Heading1'])
1188
+ title = Paragraph(f"FinOps Business Scenario: {scenario_name.title()}", styles["Heading1"])
1346
1189
  story.append(title)
1347
1190
  story.append(Spacer(1, 12))
1348
1191
 
1349
1192
  # Add results summary
1350
- if 'business_impact' in results:
1351
- business_data = results['business_impact']
1193
+ if "business_impact" in results:
1194
+ business_data = results["business_impact"]
1352
1195
  for key, value in business_data.items():
1353
- text = Paragraph(f"<b>{key.replace('_', ' ').title()}:</b> {value}",
1354
- styles['Normal'])
1196
+ text = Paragraph(f"<b>{key.replace('_', ' ').title()}:</b> {value}", styles["Normal"])
1355
1197
  story.append(text)
1356
1198
 
1357
1199
  doc.build(story)
1358
1200
  print_info(f"📋 PDF export: {output_path}")
1359
1201
 
1360
- elif report_type == 'markdown':
1202
+ elif report_type == "markdown":
1361
1203
  output_path = os.path.join(output_dir, f"{base_filename}.md")
1362
- with open(output_path, 'w', encoding='utf-8') as f:
1204
+ with open(output_path, "w", encoding="utf-8") as f:
1363
1205
  f.write(f"# FinOps Business Scenario: {scenario_name.title()}\n\n")
1364
1206
  f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
1365
1207
 
1366
1208
  # Add business impact section
1367
- if 'business_impact' in results:
1209
+ if "business_impact" in results:
1368
1210
  f.write("## Business Impact\n\n")
1369
- business_data = results['business_impact']
1211
+ business_data = results["business_impact"]
1370
1212
  for key, value in business_data.items():
1371
1213
  f.write(f"- **{key.replace('_', ' ').title()}**: {value}\n")
1372
1214
  f.write("\n")
1373
1215
 
1374
1216
  # Add technical details section
1375
- if 'technical_details' in results:
1217
+ if "technical_details" in results:
1376
1218
  f.write("## Technical Details\n\n")
1377
- tech_data = results['technical_details']
1219
+ tech_data = results["technical_details"]
1378
1220
  for key, value in tech_data.items():
1379
1221
  f.write(f"- **{key.replace('_', ' ').title()}**: {value}\n")
1380
1222
 
@@ -313,7 +313,7 @@ def handle_cost_explorer_error(error: Exception, profile_name: Optional[str] = N
313
313
 
314
314
  def _display_single_account_cost_explorer_guidance(error: Exception, profile_name: Optional[str] = None):
315
315
  """Display context-aware guidance for single account Cost Explorer limitations."""
316
-
316
+
317
317
  # Get dynamic date period for CLI examples
318
318
  start_date, end_date = get_aws_cli_example_period()
319
319