runbooks 0.9.2__py3-none-any.whl → 0.9.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 (49) hide show
  1. runbooks/__init__.py +15 -6
  2. runbooks/cfat/__init__.py +3 -1
  3. runbooks/cloudops/__init__.py +3 -1
  4. runbooks/common/aws_utils.py +367 -0
  5. runbooks/common/enhanced_logging_example.py +239 -0
  6. runbooks/common/enhanced_logging_integration_example.py +257 -0
  7. runbooks/common/logging_integration_helper.py +344 -0
  8. runbooks/common/profile_utils.py +8 -6
  9. runbooks/common/rich_utils.py +347 -3
  10. runbooks/enterprise/logging.py +400 -38
  11. runbooks/finops/README.md +262 -406
  12. runbooks/finops/__init__.py +44 -1
  13. runbooks/finops/accuracy_cross_validator.py +12 -3
  14. runbooks/finops/business_cases.py +552 -0
  15. runbooks/finops/commvault_ec2_analysis.py +415 -0
  16. runbooks/finops/cost_processor.py +718 -42
  17. runbooks/finops/dashboard_router.py +44 -22
  18. runbooks/finops/dashboard_runner.py +302 -39
  19. runbooks/finops/embedded_mcp_validator.py +358 -48
  20. runbooks/finops/finops_scenarios.py +1122 -0
  21. runbooks/finops/helpers.py +182 -0
  22. runbooks/finops/multi_dashboard.py +30 -15
  23. runbooks/finops/scenarios.py +789 -0
  24. runbooks/finops/single_dashboard.py +386 -58
  25. runbooks/finops/types.py +29 -4
  26. runbooks/inventory/__init__.py +2 -1
  27. runbooks/main.py +522 -29
  28. runbooks/operate/__init__.py +3 -1
  29. runbooks/remediation/__init__.py +3 -1
  30. runbooks/remediation/commons.py +55 -16
  31. runbooks/remediation/commvault_ec2_analysis.py +259 -0
  32. runbooks/remediation/rds_snapshot_list.py +267 -102
  33. runbooks/remediation/workspaces_list.py +182 -31
  34. runbooks/security/__init__.py +3 -1
  35. runbooks/sre/__init__.py +2 -1
  36. runbooks/utils/__init__.py +81 -6
  37. runbooks/utils/version_validator.py +241 -0
  38. runbooks/vpc/__init__.py +2 -1
  39. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/METADATA +98 -60
  40. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/RECORD +44 -39
  41. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +1 -0
  42. runbooks/inventory/cloudtrail.md +0 -727
  43. runbooks/inventory/discovery.md +0 -81
  44. runbooks/remediation/CLAUDE.md +0 -100
  45. runbooks/remediation/DOME9.md +0 -218
  46. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
  47. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
  48. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
  49. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,188 @@ console = Console()
30
30
 
31
31
  styles = getSampleStyleSheet()
32
32
 
33
+
34
+ # NOTEBOOK UTILITY FUNCTIONS - Added for clean notebook consumption
35
+ def format_currency(amount) -> str:
36
+ """
37
+ Format currency for business display in notebooks.
38
+
39
+ Args:
40
+ amount: Numeric amount to format
41
+
42
+ Returns:
43
+ Formatted currency string
44
+ """
45
+ try:
46
+ if isinstance(amount, (int, float)) and amount > 0:
47
+ return f"${amount:,.2f}"
48
+ elif amount == 0:
49
+ return "$0.00"
50
+ else:
51
+ return str(amount)
52
+ except (TypeError, ValueError):
53
+ return str(amount)
54
+
55
+
56
+ def create_business_summary_table(scenarios: Dict[str, Any], rich_console: Optional[Console] = None) -> str:
57
+ """
58
+ Create a business-friendly summary table for notebook display.
59
+
60
+ Args:
61
+ scenarios: Dictionary of business scenarios
62
+ rich_console: Optional Rich console for enhanced formatting
63
+
64
+ Returns:
65
+ Formatted table string suitable for notebook display
66
+ """
67
+ output = []
68
+
69
+ # Header
70
+ output.append("Business Case Summary")
71
+ output.append("=" * 60)
72
+ output.append(f"{'Scenario':<25} | {'Annual Savings':<15} | {'Status':<15}")
73
+ output.append("-" * 60)
74
+
75
+ # Process scenarios
76
+ for key, scenario in scenarios.items():
77
+ if key == 'metadata':
78
+ continue
79
+
80
+ # Extract scenario info
81
+ title = scenario.get('title', scenario.get('description', key))[:24]
82
+
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']
88
+ savings = f"{format_currency(range_data['min'])}-{format_currency(range_data['max'])}"
89
+ else:
90
+ savings = "Under investigation"
91
+
92
+ # Status
93
+ status = scenario.get('status', 'Analysis ready')[:14]
94
+
95
+ output.append(f"{title:<25} | {savings:<15} | {status:<15}")
96
+
97
+ # Footer
98
+ if 'metadata' in scenarios:
99
+ output.append("-" * 60)
100
+ output.append(f"Generated: {scenarios['metadata'].get('generated_at', 'Unknown')}")
101
+ output.append(f"Source: {scenarios['metadata'].get('data_source', 'Unknown')}")
102
+
103
+ return "\n".join(output)
104
+
105
+
106
+ def export_scenarios_to_notebook_html(scenarios: Dict[str, Any], title: str = "FinOps Analysis") -> str:
107
+ """
108
+ Export scenarios as HTML suitable for Jupyter notebook display.
109
+
110
+ Args:
111
+ scenarios: Dictionary of business scenarios
112
+ title: HTML document title
113
+
114
+ Returns:
115
+ HTML string for notebook display
116
+ """
117
+ html = []
118
+
119
+ # HTML header
120
+ html.append(f"""
121
+ <div style="font-family: Arial, sans-serif; margin: 20px;">
122
+ <h2 style="color: #2c5aa0;">{title}</h2>
123
+ """)
124
+
125
+ # Process scenarios
126
+ for key, scenario in scenarios.items():
127
+ if key == 'metadata':
128
+ continue
129
+
130
+ title_text = scenario.get('title', scenario.get('description', key))
131
+
132
+ html.append(f'<div style="border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px;">')
133
+ html.append(f'<h3 style="color: #1f4788; margin-top: 0;">{title_text}</h3>')
134
+
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>')
144
+ else:
145
+ html.append('<p><strong>💰 Annual Savings:</strong> Under investigation</p>')
146
+
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
+
156
+ # Metadata footer
157
+ if 'metadata' in scenarios:
158
+ metadata = scenarios['metadata']
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)
167
+
168
+
169
+ def create_roi_analysis_table(business_cases: List[Dict[str, Any]]) -> str:
170
+ """
171
+ Create ROI analysis table for business decision making.
172
+
173
+ Args:
174
+ business_cases: List of business case dictionaries
175
+
176
+ Returns:
177
+ Formatted ROI analysis table
178
+ """
179
+ output = []
180
+
181
+ # Header
182
+ output.append("ROI Analysis Summary")
183
+ output.append("=" * 80)
184
+ output.append(f"{'Business Case':<25} | {'Annual Savings':<12} | {'ROI %':<8} | {'Risk':<8} | {'Payback':<10}")
185
+ output.append("-" * 80)
186
+
187
+ total_savings = 0
188
+
189
+ # Process business cases
190
+ for case in business_cases:
191
+ title = case.get('title', 'Unknown Case')[:24]
192
+
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
+
199
+ # Format values
200
+ savings_str = format_currency(savings)[:11]
201
+ roi_str = f"{roi:.0f}%" if roi < 9999 else ">999%"
202
+ payback_str = f"{payback:.1f}mo" if payback > 0 else "Immediate"
203
+
204
+ output.append(f"{title:<25} | {savings_str:<12} | {roi_str:<8} | {risk:<8} | {payback_str:<10}")
205
+
206
+ if isinstance(savings, (int, float)):
207
+ total_savings += savings
208
+
209
+ # Summary
210
+ output.append("-" * 80)
211
+ output.append(f"{'TOTAL PORTFOLIO':<25} | {format_currency(total_savings):<12} | {'N/A':<8} | {'Mixed':<8} | {'Varies':<10}")
212
+
213
+ return "\n".join(output)
214
+
33
215
  # Custom style for the footer
34
216
  audit_footer_style = ParagraphStyle(
35
217
  name="AuditFooter",
@@ -68,12 +68,12 @@ from .cost_processor import (
68
68
  get_cost_data,
69
69
  process_service_costs,
70
70
  )
71
- from .dashboard_runner import (
72
- _create_cost_session,
73
- _create_management_session,
74
- _create_operational_session,
75
- _initialize_profiles,
71
+ from runbooks.common.profile_utils import (
72
+ create_cost_session,
73
+ create_management_session,
74
+ create_operational_session,
76
75
  )
76
+ from .dashboard_runner import _initialize_profiles
77
77
  from .enhanced_progress import track_multi_account_analysis
78
78
  from .helpers import export_cost_dashboard_to_pdf
79
79
  from .service_mapping import get_service_display_name
@@ -564,8 +564,8 @@ class MultiAccountDashboard:
564
564
  display_profile = profile
565
565
 
566
566
  # Initialize sessions using base profile
567
- cost_session = _create_cost_session(base_profile)
568
- mgmt_session = _create_management_session(base_profile)
567
+ cost_session = create_cost_session(base_profile)
568
+ mgmt_session = create_management_session(base_profile)
569
569
 
570
570
  # SRE FIX: Get account ID - use target account for Organizations API or session account
571
571
  if target_account_id:
@@ -1244,15 +1244,24 @@ class MultiAccountDashboard:
1244
1244
 
1245
1245
  top_org_services = sorted(all_services.items(), key=lambda x: x[1], reverse=True)[:5]
1246
1246
 
1247
- # Create summary panel
1248
- overall_trend = ((total_spend - total_last_month) / total_last_month * 100) if total_last_month > 0 else 0
1249
- trend_icon = "⬆" if overall_trend > 0 else "⬇" if overall_trend < 0 else "➡"
1247
+ # Create summary panel with enhanced trend analysis
1248
+ from .cost_processor import calculate_trend_with_context
1249
+
1250
+ # For multi-account analysis, we generally have full month data, but check for consistency
1251
+ overall_trend_display = calculate_trend_with_context(total_spend, total_last_month)
1252
+
1253
+ # Extract trend direction for icon (maintaining existing functionality)
1254
+ if total_last_month > 0:
1255
+ overall_trend_pct = ((total_spend - total_last_month) / total_last_month * 100)
1256
+ trend_icon = "⬆" if overall_trend_pct > 0 else "⬇" if overall_trend_pct < 0 else "➡"
1257
+ else:
1258
+ trend_icon = "➡"
1250
1259
 
1251
1260
  summary_text = f"""
1252
1261
  [highlight]Organization Summary[/]
1253
1262
  • Total Accounts: {len(accounts)}
1254
1263
  • Total Monthly Spend: {format_cost(total_spend)}
1255
- • Overall Trend: {trend_icon} {abs(overall_trend):.1f}%
1264
+ • Overall Trend: {overall_trend_display}
1256
1265
  • Budget Alerts: {over_budget_count} over budget, {warning_count} warnings
1257
1266
 
1258
1267
  [highlight]Top Organization Services[/]
@@ -1426,9 +1435,15 @@ class MultiAccountDashboard:
1426
1435
  f"| {account_display_escaped} | ${last:.0f} | ${current:.0f} | {services_text_escaped} | {budget_clean_escaped} | {optimization_escaped} |"
1427
1436
  )
1428
1437
 
1429
- # Add summary section
1430
- overall_trend = ((total_current - total_last) / total_last * 100) if total_last > 0 else 0
1431
- trend_direction = "↗️" if overall_trend > 0 else "↘️" if overall_trend < 0 else "➡️"
1438
+ # Add summary section with enhanced trend analysis
1439
+ overall_trend_display = calculate_trend_with_context(total_current, total_last)
1440
+
1441
+ # Extract trend direction for emoji (maintaining existing markdown export format)
1442
+ if total_last > 0:
1443
+ overall_trend_pct = ((total_current - total_last) / total_last * 100)
1444
+ trend_direction = "↗️" if overall_trend_pct > 0 else "↘️" if overall_trend_pct < 0 else "➡️"
1445
+ else:
1446
+ trend_direction = "➡️"
1432
1447
 
1433
1448
  lines.append("")
1434
1449
  lines.append("## Organization Summary")
@@ -1436,7 +1451,7 @@ class MultiAccountDashboard:
1436
1451
  lines.append(f"- **Total Accounts Analyzed**: {len(accounts)}")
1437
1452
  lines.append(f"- **Total Current Month**: ${total_current:,.2f}")
1438
1453
  lines.append(f"- **Total Last Month**: ${total_last:,.2f}")
1439
- lines.append(f"- **Overall Trend**: {trend_direction} {abs(overall_trend):.1f}%")
1454
+ lines.append(f"- **Overall Trend**: {overall_trend_display}")
1440
1455
  lines.append(f"- **Analysis Performance**: {execution_time:.1f}s execution")
1441
1456
  lines.append("")
1442
1457