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.
- runbooks/__init__.py +15 -6
- runbooks/cfat/__init__.py +3 -1
- runbooks/cloudops/__init__.py +3 -1
- runbooks/common/aws_utils.py +367 -0
- runbooks/common/enhanced_logging_example.py +239 -0
- runbooks/common/enhanced_logging_integration_example.py +257 -0
- runbooks/common/logging_integration_helper.py +344 -0
- runbooks/common/profile_utils.py +8 -6
- runbooks/common/rich_utils.py +347 -3
- runbooks/enterprise/logging.py +400 -38
- runbooks/finops/README.md +262 -406
- runbooks/finops/__init__.py +44 -1
- runbooks/finops/accuracy_cross_validator.py +12 -3
- runbooks/finops/business_cases.py +552 -0
- runbooks/finops/commvault_ec2_analysis.py +415 -0
- runbooks/finops/cost_processor.py +718 -42
- runbooks/finops/dashboard_router.py +44 -22
- runbooks/finops/dashboard_runner.py +302 -39
- runbooks/finops/embedded_mcp_validator.py +358 -48
- runbooks/finops/finops_scenarios.py +1122 -0
- runbooks/finops/helpers.py +182 -0
- runbooks/finops/multi_dashboard.py +30 -15
- runbooks/finops/scenarios.py +789 -0
- runbooks/finops/single_dashboard.py +386 -58
- runbooks/finops/types.py +29 -4
- runbooks/inventory/__init__.py +2 -1
- runbooks/main.py +522 -29
- runbooks/operate/__init__.py +3 -1
- runbooks/remediation/__init__.py +3 -1
- runbooks/remediation/commons.py +55 -16
- runbooks/remediation/commvault_ec2_analysis.py +259 -0
- runbooks/remediation/rds_snapshot_list.py +267 -102
- runbooks/remediation/workspaces_list.py +182 -31
- runbooks/security/__init__.py +3 -1
- runbooks/sre/__init__.py +2 -1
- runbooks/utils/__init__.py +81 -6
- runbooks/utils/version_validator.py +241 -0
- runbooks/vpc/__init__.py +2 -1
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/METADATA +98 -60
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/RECORD +44 -39
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +1 -0
- runbooks/inventory/cloudtrail.md +0 -727
- runbooks/inventory/discovery.md +0 -81
- runbooks/remediation/CLAUDE.md +0 -100
- runbooks/remediation/DOME9.md +0 -218
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
runbooks/finops/__init__.py
CHANGED
@@ -12,7 +12,8 @@ This module provides terminal-based AWS cost monitoring with features including:
|
|
12
12
|
Integrated as a submodule of CloudOps Runbooks for enterprise FinOps automation.
|
13
13
|
"""
|
14
14
|
|
15
|
-
|
15
|
+
# Import centralized version from main runbooks package
|
16
|
+
from runbooks import __version__
|
16
17
|
|
17
18
|
# Core components
|
18
19
|
# AWS client utilities
|
@@ -42,6 +43,8 @@ from runbooks.finops.dashboard_runner import (
|
|
42
43
|
# Enterprise FinOps Dashboard Components - Using existing dashboard_runner.py
|
43
44
|
# Backward compatibility module for legacy tests and components
|
44
45
|
from runbooks.finops.finops_dashboard import FinOpsConfig
|
46
|
+
|
47
|
+
# Enhanced helpers with notebook integration functions
|
45
48
|
from runbooks.finops.helpers import (
|
46
49
|
export_audit_report_to_csv,
|
47
50
|
export_audit_report_to_json,
|
@@ -49,6 +52,29 @@ from runbooks.finops.helpers import (
|
|
49
52
|
export_cost_dashboard_to_pdf,
|
50
53
|
export_trend_data_to_json,
|
51
54
|
load_config_file,
|
55
|
+
# NOTEBOOK INTEGRATION FUNCTIONS - Added for clean notebook consumption
|
56
|
+
format_currency,
|
57
|
+
create_business_summary_table,
|
58
|
+
export_scenarios_to_notebook_html,
|
59
|
+
create_roi_analysis_table,
|
60
|
+
)
|
61
|
+
|
62
|
+
# Business scenarios with notebook integration
|
63
|
+
from runbooks.finops.finops_scenarios import (
|
64
|
+
create_business_scenarios_validated,
|
65
|
+
format_for_business_audience,
|
66
|
+
format_for_technical_audience,
|
67
|
+
FinOpsBusinessScenarios,
|
68
|
+
)
|
69
|
+
|
70
|
+
# NEW v0.9.5: Clean API wrapper for notebook consumption
|
71
|
+
from runbooks.finops.scenarios import (
|
72
|
+
finops_24_workspaces_cleanup,
|
73
|
+
finops_23_rds_snapshots_optimization,
|
74
|
+
finops_25_commvault_investigation,
|
75
|
+
get_business_scenarios_summary,
|
76
|
+
format_for_audience,
|
77
|
+
validate_scenarios_accuracy,
|
52
78
|
)
|
53
79
|
from runbooks.finops.profile_processor import process_combined_profiles, process_single_profile
|
54
80
|
|
@@ -69,6 +95,18 @@ __all__ = [
|
|
69
95
|
"_run_executive_dashboard",
|
70
96
|
# Enterprise Dashboard Classes - backward compatibility
|
71
97
|
"FinOpsConfig",
|
98
|
+
# Business scenarios with notebook integration (v0.9.5)
|
99
|
+
"create_business_scenarios_validated",
|
100
|
+
"format_for_business_audience",
|
101
|
+
"format_for_technical_audience",
|
102
|
+
"FinOpsBusinessScenarios",
|
103
|
+
# NEW v0.9.5: Clean API wrapper functions
|
104
|
+
"finops_24_workspaces_cleanup",
|
105
|
+
"finops_23_rds_snapshots_optimization",
|
106
|
+
"finops_25_commvault_investigation",
|
107
|
+
"get_business_scenarios_summary",
|
108
|
+
"format_for_audience",
|
109
|
+
"validate_scenarios_accuracy",
|
72
110
|
# Processors
|
73
111
|
"get_cost_data",
|
74
112
|
"get_trend",
|
@@ -94,6 +132,11 @@ __all__ = [
|
|
94
132
|
"export_audit_report_to_json",
|
95
133
|
"export_trend_data_to_json",
|
96
134
|
"load_config_file",
|
135
|
+
# NOTEBOOK INTEGRATION FUNCTIONS (v0.9.5)
|
136
|
+
"format_currency",
|
137
|
+
"create_business_summary_table",
|
138
|
+
"export_scenarios_to_notebook_html",
|
139
|
+
"create_roi_analysis_table",
|
97
140
|
# Types
|
98
141
|
"ProfileData",
|
99
142
|
"CostData",
|
@@ -379,14 +379,23 @@ class AccuracyCrossValidator:
|
|
379
379
|
session = boto3.Session(profile_name=profile)
|
380
380
|
ce_client = session.client("ce", region_name="us-east-1")
|
381
381
|
|
382
|
-
# Get current month cost data
|
382
|
+
# Get current month cost data with September 1st fix
|
383
383
|
end_date = datetime.now().date()
|
384
384
|
start_date = end_date.replace(day=1)
|
385
|
+
|
386
|
+
# CRITICAL FIX: September 1st boundary handling (matches cost_processor.py)
|
387
|
+
if end_date.day == 1:
|
388
|
+
self.console.log(f"[yellow]⚠️ Cross-Validator: First day of month detected ({end_date.strftime('%B %d, %Y')}) - using partial period[/]")
|
389
|
+
# For AWS Cost Explorer, end date is exclusive, so add one day to include today
|
390
|
+
end_date = end_date + timedelta(days=1)
|
391
|
+
else:
|
392
|
+
# Normal case: include up to today (exclusive end date)
|
393
|
+
end_date = end_date + timedelta(days=1)
|
385
394
|
|
386
395
|
response = ce_client.get_cost_and_usage(
|
387
396
|
TimePeriod={"Start": start_date.isoformat(), "End": end_date.isoformat()},
|
388
397
|
Granularity="MONTHLY",
|
389
|
-
Metrics=["
|
398
|
+
Metrics=["UnblendedCost"],
|
390
399
|
GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
|
391
400
|
)
|
392
401
|
|
@@ -398,7 +407,7 @@ class AccuracyCrossValidator:
|
|
398
407
|
for result in response["ResultsByTime"]:
|
399
408
|
for group in result.get("Groups", []):
|
400
409
|
service = group.get("Keys", ["Unknown"])[0]
|
401
|
-
cost = float(group.get("Metrics", {}).get("
|
410
|
+
cost = float(group.get("Metrics", {}).get("UnblendedCost", {}).get("Amount", 0))
|
402
411
|
services[service] = cost
|
403
412
|
total_cost += cost
|
404
413
|
|
@@ -0,0 +1,552 @@
|
|
1
|
+
"""
|
2
|
+
FinOps Business Case Analysis Framework
|
3
|
+
=====================================
|
4
|
+
|
5
|
+
Enterprise-grade business case analysis for cost optimization scenarios.
|
6
|
+
This module provides reusable business case analyzers that work across
|
7
|
+
multiple enterprises and projects.
|
8
|
+
|
9
|
+
Key Features:
|
10
|
+
- Real AWS data integration (no hardcoded values)
|
11
|
+
- ROI calculation methodologies
|
12
|
+
- Risk assessment frameworks
|
13
|
+
- Timeline estimation algorithms
|
14
|
+
- Multi-enterprise configuration support
|
15
|
+
|
16
|
+
Author: Enterprise Agile Team
|
17
|
+
Version: 0.9.5
|
18
|
+
"""
|
19
|
+
|
20
|
+
import os
|
21
|
+
import json
|
22
|
+
import subprocess
|
23
|
+
from typing import Dict, List, Optional, Any, Union
|
24
|
+
from datetime import datetime, timedelta
|
25
|
+
from dataclasses import dataclass
|
26
|
+
from enum import Enum
|
27
|
+
|
28
|
+
from ..common.rich_utils import console, format_cost
|
29
|
+
|
30
|
+
|
31
|
+
class RiskLevel(Enum):
|
32
|
+
"""Business risk levels for cost optimization initiatives"""
|
33
|
+
LOW = "Low"
|
34
|
+
MEDIUM = "Medium"
|
35
|
+
HIGH = "High"
|
36
|
+
CRITICAL = "Critical"
|
37
|
+
|
38
|
+
|
39
|
+
class BusinessCaseStatus(Enum):
|
40
|
+
"""Business case lifecycle status"""
|
41
|
+
INVESTIGATION = "Investigation Phase"
|
42
|
+
ANALYSIS = "Analysis Complete"
|
43
|
+
APPROVED = "Approved for Implementation"
|
44
|
+
IN_PROGRESS = "Implementation In Progress"
|
45
|
+
COMPLETED = "Implementation Complete"
|
46
|
+
CANCELLED = "Cancelled"
|
47
|
+
|
48
|
+
|
49
|
+
@dataclass
|
50
|
+
class ROIMetrics:
|
51
|
+
"""ROI calculation results"""
|
52
|
+
annual_savings: float
|
53
|
+
implementation_cost: float
|
54
|
+
roi_percentage: float
|
55
|
+
payback_months: float
|
56
|
+
net_first_year: float
|
57
|
+
risk_adjusted_savings: float
|
58
|
+
|
59
|
+
|
60
|
+
@dataclass
|
61
|
+
class BusinessCase:
|
62
|
+
"""Complete business case analysis"""
|
63
|
+
title: str
|
64
|
+
scenario_key: str
|
65
|
+
status: BusinessCaseStatus
|
66
|
+
risk_level: RiskLevel
|
67
|
+
roi_metrics: ROIMetrics
|
68
|
+
implementation_time: str
|
69
|
+
resource_count: int
|
70
|
+
affected_accounts: List[str]
|
71
|
+
next_steps: List[str]
|
72
|
+
data_source: str
|
73
|
+
validation_status: str
|
74
|
+
timestamp: str
|
75
|
+
|
76
|
+
|
77
|
+
class BusinessCaseAnalyzer:
|
78
|
+
"""
|
79
|
+
Enterprise business case analyzer for cost optimization scenarios.
|
80
|
+
|
81
|
+
This class provides reusable business case analysis capabilities that
|
82
|
+
can be used across multiple enterprises and projects.
|
83
|
+
"""
|
84
|
+
|
85
|
+
def __init__(self, profile: Optional[str] = None, enterprise_config: Optional[Dict] = None):
|
86
|
+
"""
|
87
|
+
Initialize business case analyzer.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
profile: AWS profile for data collection
|
91
|
+
enterprise_config: Enterprise-specific configuration
|
92
|
+
"""
|
93
|
+
self.profile = profile or os.getenv('AWS_PROFILE')
|
94
|
+
self.enterprise_config = enterprise_config or {}
|
95
|
+
self.runbooks_cmd = 'runbooks'
|
96
|
+
|
97
|
+
# Enterprise cost configuration
|
98
|
+
self.hourly_rate = self.enterprise_config.get('technical_hourly_rate', 150)
|
99
|
+
self.risk_multipliers = self.enterprise_config.get('risk_multipliers', {
|
100
|
+
RiskLevel.LOW: 1.0,
|
101
|
+
RiskLevel.MEDIUM: 0.85,
|
102
|
+
RiskLevel.HIGH: 0.7,
|
103
|
+
RiskLevel.CRITICAL: 0.5
|
104
|
+
})
|
105
|
+
|
106
|
+
def execute_runbooks_command(self, command_args: List[str], json_output: bool = True) -> Dict[str, Any]:
|
107
|
+
"""
|
108
|
+
Execute runbooks CLI command for data collection.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
command_args: CLI command arguments
|
112
|
+
json_output: Whether to parse JSON output
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
Command results or error information
|
116
|
+
"""
|
117
|
+
cmd = [self.runbooks_cmd] + command_args
|
118
|
+
|
119
|
+
if self.profile:
|
120
|
+
cmd.extend(['--profile', self.profile])
|
121
|
+
|
122
|
+
if json_output:
|
123
|
+
cmd.append('--json')
|
124
|
+
|
125
|
+
try:
|
126
|
+
result = subprocess.run(
|
127
|
+
cmd,
|
128
|
+
capture_output=True,
|
129
|
+
text=True,
|
130
|
+
check=True,
|
131
|
+
timeout=60 # 1 minute timeout for CLI operations
|
132
|
+
)
|
133
|
+
|
134
|
+
if json_output:
|
135
|
+
return json.loads(result.stdout)
|
136
|
+
return {'stdout': result.stdout, 'success': True}
|
137
|
+
|
138
|
+
except subprocess.CalledProcessError as e:
|
139
|
+
return {
|
140
|
+
'error': True,
|
141
|
+
'message': f"CLI command failed: {e}",
|
142
|
+
'stderr': e.stderr,
|
143
|
+
'returncode': e.returncode
|
144
|
+
}
|
145
|
+
except subprocess.TimeoutExpired:
|
146
|
+
return {
|
147
|
+
'error': True,
|
148
|
+
'message': "CLI command timeout after 60 seconds",
|
149
|
+
'timeout': True
|
150
|
+
}
|
151
|
+
except json.JSONDecodeError as e:
|
152
|
+
return {
|
153
|
+
'error': True,
|
154
|
+
'message': f"Failed to parse JSON output: {e}",
|
155
|
+
'raw_output': result.stdout
|
156
|
+
}
|
157
|
+
except Exception as e:
|
158
|
+
return {
|
159
|
+
'error': True,
|
160
|
+
'message': f"Unexpected error: {e}"
|
161
|
+
}
|
162
|
+
|
163
|
+
def calculate_roi_metrics(
|
164
|
+
self,
|
165
|
+
annual_savings: float,
|
166
|
+
implementation_hours: float = 8,
|
167
|
+
additional_costs: float = 0,
|
168
|
+
risk_level: RiskLevel = RiskLevel.MEDIUM
|
169
|
+
) -> ROIMetrics:
|
170
|
+
"""
|
171
|
+
Calculate comprehensive ROI metrics for business case analysis.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
annual_savings: Projected annual cost savings
|
175
|
+
implementation_hours: Estimated implementation time in hours
|
176
|
+
additional_costs: Additional implementation costs (tools, training, etc.)
|
177
|
+
risk_level: Business risk assessment
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Complete ROI metrics analysis
|
181
|
+
"""
|
182
|
+
# Calculate total implementation cost
|
183
|
+
labor_cost = implementation_hours * self.hourly_rate
|
184
|
+
total_implementation_cost = labor_cost + additional_costs
|
185
|
+
|
186
|
+
# Risk-adjusted savings calculation
|
187
|
+
risk_multiplier = self.risk_multipliers.get(risk_level, 0.85)
|
188
|
+
risk_adjusted_savings = annual_savings * risk_multiplier
|
189
|
+
|
190
|
+
# ROI calculations
|
191
|
+
if total_implementation_cost > 0:
|
192
|
+
roi_percentage = ((risk_adjusted_savings - total_implementation_cost) / total_implementation_cost) * 100
|
193
|
+
payback_months = (total_implementation_cost / annual_savings) * 12 if annual_savings > 0 else 0
|
194
|
+
else:
|
195
|
+
roi_percentage = float('inf')
|
196
|
+
payback_months = 0
|
197
|
+
|
198
|
+
net_first_year = risk_adjusted_savings - total_implementation_cost
|
199
|
+
|
200
|
+
return ROIMetrics(
|
201
|
+
annual_savings=annual_savings,
|
202
|
+
implementation_cost=total_implementation_cost,
|
203
|
+
roi_percentage=roi_percentage,
|
204
|
+
payback_months=payback_months,
|
205
|
+
net_first_year=net_first_year,
|
206
|
+
risk_adjusted_savings=risk_adjusted_savings
|
207
|
+
)
|
208
|
+
|
209
|
+
def analyze_workspaces_scenario(self) -> BusinessCase:
|
210
|
+
"""
|
211
|
+
Analyze WorkSpaces cleanup business case using real AWS data.
|
212
|
+
|
213
|
+
Returns:
|
214
|
+
Complete WorkSpaces business case analysis
|
215
|
+
"""
|
216
|
+
# Get real data from runbooks CLI
|
217
|
+
data = self.execute_runbooks_command(['finops', '--scenario', 'workspaces'])
|
218
|
+
|
219
|
+
if data.get('error'):
|
220
|
+
# Return error case for handling
|
221
|
+
return BusinessCase(
|
222
|
+
title="WorkSpaces Cleanup Initiative",
|
223
|
+
scenario_key="workspaces",
|
224
|
+
status=BusinessCaseStatus.INVESTIGATION,
|
225
|
+
risk_level=RiskLevel.MEDIUM,
|
226
|
+
roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
|
227
|
+
implementation_time="Pending data collection",
|
228
|
+
resource_count=0,
|
229
|
+
affected_accounts=[],
|
230
|
+
next_steps=["Connect to AWS environment for data collection"],
|
231
|
+
data_source=f"Error: {data.get('message', 'Unknown error')}",
|
232
|
+
validation_status="Failed - no data available",
|
233
|
+
timestamp=datetime.now().isoformat()
|
234
|
+
)
|
235
|
+
|
236
|
+
# Extract real data from CLI response
|
237
|
+
unused_workspaces = data.get('unused_workspaces', [])
|
238
|
+
|
239
|
+
# Calculate actual savings from real data
|
240
|
+
annual_savings = sum(
|
241
|
+
ws.get('monthly_cost', 0) * 12
|
242
|
+
for ws in unused_workspaces
|
243
|
+
)
|
244
|
+
|
245
|
+
# Get unique accounts
|
246
|
+
unique_accounts = list(set(
|
247
|
+
ws.get('account_id')
|
248
|
+
for ws in unused_workspaces
|
249
|
+
if ws.get('account_id')
|
250
|
+
))
|
251
|
+
|
252
|
+
# Estimate implementation time based on resource count
|
253
|
+
resource_count = len(unused_workspaces)
|
254
|
+
if resource_count <= 10:
|
255
|
+
implementation_time = "4-6 hours"
|
256
|
+
implementation_hours = 6
|
257
|
+
elif resource_count <= 25:
|
258
|
+
implementation_time = "6-8 hours"
|
259
|
+
implementation_hours = 8
|
260
|
+
else:
|
261
|
+
implementation_time = "1-2 days"
|
262
|
+
implementation_hours = 16
|
263
|
+
|
264
|
+
# Calculate ROI metrics
|
265
|
+
roi_metrics = self.calculate_roi_metrics(
|
266
|
+
annual_savings=annual_savings,
|
267
|
+
implementation_hours=implementation_hours,
|
268
|
+
risk_level=RiskLevel.LOW # WorkSpaces deletion is low risk
|
269
|
+
)
|
270
|
+
|
271
|
+
return BusinessCase(
|
272
|
+
title="WorkSpaces Cleanup Initiative",
|
273
|
+
scenario_key="workspaces",
|
274
|
+
status=BusinessCaseStatus.ANALYSIS,
|
275
|
+
risk_level=RiskLevel.LOW,
|
276
|
+
roi_metrics=roi_metrics,
|
277
|
+
implementation_time=implementation_time,
|
278
|
+
resource_count=resource_count,
|
279
|
+
affected_accounts=unique_accounts,
|
280
|
+
next_steps=[
|
281
|
+
"Review unused WorkSpaces list with business stakeholders",
|
282
|
+
"Schedule maintenance window for WorkSpaces deletion",
|
283
|
+
"Execute cleanup during planned maintenance",
|
284
|
+
"Validate cost reduction in next billing cycle"
|
285
|
+
],
|
286
|
+
data_source="Real AWS API via runbooks CLI",
|
287
|
+
validation_status=data.get('validation_status', 'CLI validated'),
|
288
|
+
timestamp=datetime.now().isoformat()
|
289
|
+
)
|
290
|
+
|
291
|
+
def analyze_rds_snapshots_scenario(self) -> BusinessCase:
|
292
|
+
"""
|
293
|
+
Analyze RDS snapshots cleanup business case using real AWS data.
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
Complete RDS snapshots business case analysis
|
297
|
+
"""
|
298
|
+
# Get real data from runbooks CLI
|
299
|
+
data = self.execute_runbooks_command(['finops', '--scenario', 'snapshots'])
|
300
|
+
|
301
|
+
if data.get('error'):
|
302
|
+
return BusinessCase(
|
303
|
+
title="RDS Storage Optimization",
|
304
|
+
scenario_key="rds_snapshots",
|
305
|
+
status=BusinessCaseStatus.INVESTIGATION,
|
306
|
+
risk_level=RiskLevel.MEDIUM,
|
307
|
+
roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
|
308
|
+
implementation_time="Pending data collection",
|
309
|
+
resource_count=0,
|
310
|
+
affected_accounts=[],
|
311
|
+
next_steps=["Connect to AWS environment for data collection"],
|
312
|
+
data_source=f"Error: {data.get('message', 'Unknown error')}",
|
313
|
+
validation_status="Failed - no data available",
|
314
|
+
timestamp=datetime.now().isoformat()
|
315
|
+
)
|
316
|
+
|
317
|
+
# Extract real snapshot data
|
318
|
+
snapshots = data.get('manual_snapshots', [])
|
319
|
+
|
320
|
+
# Calculate storage and costs
|
321
|
+
total_storage_gb = sum(
|
322
|
+
s.get('size_gb', 0)
|
323
|
+
for s in snapshots
|
324
|
+
)
|
325
|
+
|
326
|
+
# AWS snapshot storage pricing (current as of 2024)
|
327
|
+
cost_per_gb_month = 0.095
|
328
|
+
|
329
|
+
# Conservative savings estimate (assume 70% can be safely deleted)
|
330
|
+
conservative_savings = total_storage_gb * cost_per_gb_month * 12 * 0.7
|
331
|
+
|
332
|
+
# Get unique accounts
|
333
|
+
unique_accounts = list(set(
|
334
|
+
s.get('account_id')
|
335
|
+
for s in snapshots
|
336
|
+
if s.get('account_id')
|
337
|
+
))
|
338
|
+
|
339
|
+
# Estimate implementation time based on accounts and snapshots
|
340
|
+
account_count = len(unique_accounts)
|
341
|
+
resource_count = len(snapshots)
|
342
|
+
implementation_hours = max(8, account_count * 4) # Minimum 8 hours, 4 hours per account
|
343
|
+
implementation_time = f"{implementation_hours//8}-{(implementation_hours//8)+1} days"
|
344
|
+
|
345
|
+
# Calculate ROI metrics
|
346
|
+
roi_metrics = self.calculate_roi_metrics(
|
347
|
+
annual_savings=conservative_savings,
|
348
|
+
implementation_hours=implementation_hours,
|
349
|
+
risk_level=RiskLevel.MEDIUM # RDS snapshots require careful analysis
|
350
|
+
)
|
351
|
+
|
352
|
+
return BusinessCase(
|
353
|
+
title="RDS Storage Optimization",
|
354
|
+
scenario_key="rds_snapshots",
|
355
|
+
status=BusinessCaseStatus.ANALYSIS,
|
356
|
+
risk_level=RiskLevel.MEDIUM,
|
357
|
+
roi_metrics=roi_metrics,
|
358
|
+
implementation_time=implementation_time,
|
359
|
+
resource_count=resource_count,
|
360
|
+
affected_accounts=unique_accounts,
|
361
|
+
next_steps=[
|
362
|
+
"Review snapshot retention policies with database teams",
|
363
|
+
"Identify snapshots safe for deletion (>30 days old)",
|
364
|
+
"Create automated cleanup policies for ongoing management",
|
365
|
+
"Implement lifecycle policies for future snapshots"
|
366
|
+
],
|
367
|
+
data_source="Real AWS API via runbooks CLI",
|
368
|
+
validation_status=data.get('validation_status', 'CLI validated'),
|
369
|
+
timestamp=datetime.now().isoformat()
|
370
|
+
)
|
371
|
+
|
372
|
+
def analyze_commvault_scenario(self) -> BusinessCase:
|
373
|
+
"""
|
374
|
+
Analyze Commvault infrastructure investigation case.
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
Complete Commvault investigation business case
|
378
|
+
"""
|
379
|
+
# Get real data from runbooks CLI
|
380
|
+
data = self.execute_runbooks_command(['finops', '--scenario', 'commvault'])
|
381
|
+
|
382
|
+
if data.get('error'):
|
383
|
+
return BusinessCase(
|
384
|
+
title="Infrastructure Utilization Investigation",
|
385
|
+
scenario_key="commvault",
|
386
|
+
status=BusinessCaseStatus.INVESTIGATION,
|
387
|
+
risk_level=RiskLevel.MEDIUM,
|
388
|
+
roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
|
389
|
+
implementation_time="Investigation phase",
|
390
|
+
resource_count=0,
|
391
|
+
affected_accounts=[],
|
392
|
+
next_steps=["Connect to AWS environment for data collection"],
|
393
|
+
data_source=f"Error: {data.get('message', 'Unknown error')}",
|
394
|
+
validation_status="Failed - no data available",
|
395
|
+
timestamp=datetime.now().isoformat()
|
396
|
+
)
|
397
|
+
|
398
|
+
# This scenario is in investigation phase - no concrete savings yet
|
399
|
+
account_id = data.get('account_id', 'Unknown')
|
400
|
+
|
401
|
+
return BusinessCase(
|
402
|
+
title="Infrastructure Utilization Investigation",
|
403
|
+
scenario_key="commvault",
|
404
|
+
status=BusinessCaseStatus.INVESTIGATION,
|
405
|
+
risk_level=RiskLevel.MEDIUM,
|
406
|
+
roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0), # No concrete savings yet
|
407
|
+
implementation_time="Assessment: 1-2 days, Implementation: TBD",
|
408
|
+
resource_count=0, # Will be determined during investigation
|
409
|
+
affected_accounts=[account_id] if account_id != 'Unknown' else [],
|
410
|
+
next_steps=[
|
411
|
+
"Analyze EC2 utilization metrics for all instances",
|
412
|
+
"Determine if instances are actively used by applications",
|
413
|
+
"Calculate potential savings IF decommissioning is viable",
|
414
|
+
"Develop implementation plan based on utilization analysis"
|
415
|
+
],
|
416
|
+
data_source="Investigation framework via runbooks CLI",
|
417
|
+
validation_status=data.get('validation_status', 'Investigation phase'),
|
418
|
+
timestamp=datetime.now().isoformat()
|
419
|
+
)
|
420
|
+
|
421
|
+
def get_all_business_cases(self) -> Dict[str, BusinessCase]:
|
422
|
+
"""
|
423
|
+
Analyze all available business cases and return comprehensive results.
|
424
|
+
|
425
|
+
Returns:
|
426
|
+
Dictionary of all business case analyses
|
427
|
+
"""
|
428
|
+
cases = {
|
429
|
+
'workspaces': self.analyze_workspaces_scenario(),
|
430
|
+
'rds_snapshots': self.analyze_rds_snapshots_scenario(),
|
431
|
+
'commvault': self.analyze_commvault_scenario()
|
432
|
+
}
|
433
|
+
|
434
|
+
return cases
|
435
|
+
|
436
|
+
def calculate_portfolio_roi(self, business_cases: Dict[str, BusinessCase]) -> Dict[str, Any]:
|
437
|
+
"""
|
438
|
+
Calculate portfolio-level ROI across all business cases.
|
439
|
+
|
440
|
+
Args:
|
441
|
+
business_cases: Dictionary of business case analyses
|
442
|
+
|
443
|
+
Returns:
|
444
|
+
Portfolio ROI analysis
|
445
|
+
"""
|
446
|
+
total_annual_savings = 0
|
447
|
+
total_implementation_cost = 0
|
448
|
+
total_risk_adjusted_savings = 0
|
449
|
+
|
450
|
+
for case in business_cases.values():
|
451
|
+
if case.roi_metrics:
|
452
|
+
total_annual_savings += case.roi_metrics.annual_savings
|
453
|
+
total_implementation_cost += case.roi_metrics.implementation_cost
|
454
|
+
total_risk_adjusted_savings += case.roi_metrics.risk_adjusted_savings
|
455
|
+
|
456
|
+
if total_implementation_cost > 0:
|
457
|
+
portfolio_roi = ((total_risk_adjusted_savings - total_implementation_cost) / total_implementation_cost) * 100
|
458
|
+
portfolio_payback = (total_implementation_cost / total_annual_savings) * 12 if total_annual_savings > 0 else 0
|
459
|
+
else:
|
460
|
+
portfolio_roi = 0
|
461
|
+
portfolio_payback = 0
|
462
|
+
|
463
|
+
return {
|
464
|
+
'total_annual_savings': total_annual_savings,
|
465
|
+
'total_implementation_cost': total_implementation_cost,
|
466
|
+
'total_risk_adjusted_savings': total_risk_adjusted_savings,
|
467
|
+
'portfolio_roi_percentage': portfolio_roi,
|
468
|
+
'portfolio_payback_months': portfolio_payback,
|
469
|
+
'net_first_year_value': total_risk_adjusted_savings - total_implementation_cost,
|
470
|
+
'analysis_timestamp': datetime.now().isoformat()
|
471
|
+
}
|
472
|
+
|
473
|
+
|
474
|
+
class BusinessCaseFormatter:
|
475
|
+
"""Format business cases for different audiences"""
|
476
|
+
|
477
|
+
@staticmethod
|
478
|
+
def format_for_business_audience(business_cases: Dict[str, BusinessCase]) -> str:
|
479
|
+
"""
|
480
|
+
Format business cases for manager/financial audience.
|
481
|
+
|
482
|
+
Args:
|
483
|
+
business_cases: Dictionary of business case analyses
|
484
|
+
|
485
|
+
Returns:
|
486
|
+
Business-friendly formatted summary
|
487
|
+
"""
|
488
|
+
output = []
|
489
|
+
output.append("Executive Summary - Cost Optimization Business Cases")
|
490
|
+
output.append("=" * 60)
|
491
|
+
|
492
|
+
for case in business_cases.values():
|
493
|
+
output.append(f"\n📋 {case.title}")
|
494
|
+
output.append(f" Status: {case.status.value}")
|
495
|
+
|
496
|
+
if case.roi_metrics.annual_savings > 0:
|
497
|
+
output.append(f" 💰 Annual Savings: {format_cost(case.roi_metrics.annual_savings)}")
|
498
|
+
output.append(f" 📈 ROI: {case.roi_metrics.roi_percentage:.0f}%")
|
499
|
+
output.append(f" ⏱️ Payback: {case.roi_metrics.payback_months:.1f} months")
|
500
|
+
else:
|
501
|
+
output.append(f" 💰 Annual Savings: Under investigation")
|
502
|
+
|
503
|
+
output.append(f" 🛡️ Risk Level: {case.risk_level.value}")
|
504
|
+
output.append(f" ⏰ Implementation Time: {case.implementation_time}")
|
505
|
+
|
506
|
+
if case.resource_count > 0:
|
507
|
+
output.append(f" 📊 Resources: {case.resource_count} items")
|
508
|
+
|
509
|
+
return "\n".join(output)
|
510
|
+
|
511
|
+
@staticmethod
|
512
|
+
def format_for_technical_audience(business_cases: Dict[str, BusinessCase]) -> str:
|
513
|
+
"""
|
514
|
+
Format business cases for technical audience.
|
515
|
+
|
516
|
+
Args:
|
517
|
+
business_cases: Dictionary of business case analyses
|
518
|
+
|
519
|
+
Returns:
|
520
|
+
Technical implementation details
|
521
|
+
"""
|
522
|
+
output = []
|
523
|
+
output.append("Technical Implementation Guide - FinOps Business Cases")
|
524
|
+
output.append("=" * 60)
|
525
|
+
|
526
|
+
for key, case in business_cases.items():
|
527
|
+
output.append(f"\n🔧 {case.title}")
|
528
|
+
output.append(f" Scenario Key: {case.scenario_key}")
|
529
|
+
output.append(f" Data Source: {case.data_source}")
|
530
|
+
output.append(f" Validation: {case.validation_status}")
|
531
|
+
|
532
|
+
if case.affected_accounts:
|
533
|
+
output.append(f" Affected Accounts: {', '.join(case.affected_accounts)}")
|
534
|
+
|
535
|
+
output.append(f" Resource Count: {case.resource_count}")
|
536
|
+
|
537
|
+
# CLI commands for implementation
|
538
|
+
output.append(f"\n CLI Implementation:")
|
539
|
+
output.append(f" runbooks finops --scenario {key} --validate")
|
540
|
+
|
541
|
+
if key == 'workspaces':
|
542
|
+
output.append(f" runbooks finops --scenario workspaces --delete --dry-run")
|
543
|
+
elif key == 'rds_snapshots':
|
544
|
+
output.append(f" runbooks finops --scenario snapshots --cleanup --dry-run")
|
545
|
+
elif key == 'commvault':
|
546
|
+
output.append(f" runbooks finops --scenario commvault --investigate")
|
547
|
+
|
548
|
+
output.append(f"\n Next Steps:")
|
549
|
+
for step in case.next_steps:
|
550
|
+
output.append(f" • {step}")
|
551
|
+
|
552
|
+
return "\n".join(output)
|