runbooks 1.0.0__py3-none-any.whl → 1.0.2__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 (99) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  6. runbooks/cfat/weight_config.ts +574 -0
  7. runbooks/cloudops/models.py +20 -14
  8. runbooks/common/__init__.py +26 -9
  9. runbooks/common/aws_pricing.py +1070 -105
  10. runbooks/common/aws_pricing_api.py +276 -44
  11. runbooks/common/date_utils.py +115 -0
  12. runbooks/common/dry_run_examples.py +587 -0
  13. runbooks/common/dry_run_framework.py +520 -0
  14. runbooks/common/enhanced_exception_handler.py +10 -7
  15. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  16. runbooks/common/memory_optimization.py +533 -0
  17. runbooks/common/performance_optimization_engine.py +1153 -0
  18. runbooks/common/profile_utils.py +86 -118
  19. runbooks/common/rich_utils.py +3 -3
  20. runbooks/common/sre_performance_suite.py +574 -0
  21. runbooks/finops/business_case_config.py +314 -0
  22. runbooks/finops/cost_processor.py +19 -4
  23. runbooks/finops/dashboard_runner.py +47 -28
  24. runbooks/finops/ebs_cost_optimizer.py +1 -1
  25. runbooks/finops/ebs_optimizer.py +56 -9
  26. runbooks/finops/embedded_mcp_validator.py +642 -36
  27. runbooks/finops/enhanced_trend_visualization.py +7 -2
  28. runbooks/finops/executive_export.py +789 -0
  29. runbooks/finops/finops_dashboard.py +6 -5
  30. runbooks/finops/finops_scenarios.py +34 -27
  31. runbooks/finops/iam_guidance.py +6 -1
  32. runbooks/finops/nat_gateway_optimizer.py +46 -27
  33. runbooks/finops/notebook_utils.py +1 -1
  34. runbooks/finops/schemas.py +73 -58
  35. runbooks/finops/single_dashboard.py +20 -4
  36. runbooks/finops/tests/test_integration.py +3 -1
  37. runbooks/finops/vpc_cleanup_exporter.py +2 -1
  38. runbooks/finops/vpc_cleanup_optimizer.py +22 -29
  39. runbooks/inventory/core/collector.py +51 -28
  40. runbooks/inventory/discovery.md +197 -247
  41. runbooks/inventory/inventory_modules.py +2 -2
  42. runbooks/inventory/list_ec2_instances.py +3 -3
  43. runbooks/inventory/models/account.py +5 -3
  44. runbooks/inventory/models/inventory.py +1 -1
  45. runbooks/inventory/models/resource.py +5 -3
  46. runbooks/inventory/organizations_discovery.py +102 -13
  47. runbooks/inventory/unified_validation_engine.py +2 -15
  48. runbooks/main.py +255 -92
  49. runbooks/operate/base.py +9 -6
  50. runbooks/operate/deployment_framework.py +5 -4
  51. runbooks/operate/deployment_validator.py +6 -5
  52. runbooks/operate/mcp_integration.py +6 -5
  53. runbooks/operate/networking_cost_heatmap.py +17 -13
  54. runbooks/operate/vpc_operations.py +82 -13
  55. runbooks/remediation/base.py +3 -1
  56. runbooks/remediation/commons.py +5 -5
  57. runbooks/remediation/commvault_ec2_analysis.py +66 -18
  58. runbooks/remediation/config/accounts_example.json +31 -0
  59. runbooks/remediation/multi_account.py +120 -7
  60. runbooks/remediation/remediation_cli.py +710 -0
  61. runbooks/remediation/universal_account_discovery.py +377 -0
  62. runbooks/remediation/workspaces_list.py +2 -2
  63. runbooks/security/compliance_automation_engine.py +99 -20
  64. runbooks/security/config/__init__.py +24 -0
  65. runbooks/security/config/compliance_config.py +255 -0
  66. runbooks/security/config/compliance_weights_example.json +22 -0
  67. runbooks/security/config_template_generator.py +500 -0
  68. runbooks/security/security_cli.py +377 -0
  69. runbooks/validation/cli.py +8 -7
  70. runbooks/validation/comprehensive_2way_validator.py +26 -15
  71. runbooks/validation/mcp_validator.py +62 -8
  72. runbooks/vpc/config.py +49 -15
  73. runbooks/vpc/cross_account_session.py +5 -1
  74. runbooks/vpc/heatmap_engine.py +438 -59
  75. runbooks/vpc/mcp_no_eni_validator.py +115 -36
  76. runbooks/vpc/performance_optimized_analyzer.py +546 -0
  77. runbooks/vpc/runbooks_adapter.py +33 -12
  78. runbooks/vpc/tests/conftest.py +4 -2
  79. runbooks/vpc/tests/test_cost_engine.py +3 -1
  80. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/METADATA +1 -1
  81. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/RECORD +85 -79
  82. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  83. runbooks/finops/runbooks.security.report_generator.log +0 -0
  84. runbooks/finops/runbooks.security.run_script.log +0 -0
  85. runbooks/finops/runbooks.security.security_export.log +0 -0
  86. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  87. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  88. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  89. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  90. runbooks/inventory/runbooks.security.run_script.log +0 -0
  91. runbooks/inventory/runbooks.security.security_export.log +0 -0
  92. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  93. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  94. runbooks/vpc/runbooks.security.run_script.log +0 -0
  95. runbooks/vpc/runbooks.security.security_export.log +0 -0
  96. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/WHEEL +0 -0
  97. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/entry_points.txt +0 -0
  98. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/licenses/LICENSE +0 -0
  99. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,314 @@
1
+ """
2
+ Dynamic Business Case Configuration - Enterprise Template System
3
+
4
+ Strategic Achievement: Replace hardcoded JIRA references with dynamic business case templates
5
+ - Enterprise naming conventions with configurable business scenarios
6
+ - Dynamic financial targets and achievement tracking
7
+ - Reusable template system for unlimited business case scaling
8
+
9
+ This module provides configurable business case templates following enterprise standards:
10
+ - "Do one thing and do it well": Centralized configuration management
11
+ - "Move Fast, But Not So Fast We Crash": Proven template patterns with validation
12
+ """
13
+
14
+ import os
15
+ from dataclasses import dataclass
16
+ from typing import Dict, List, Optional, Any, Union
17
+ from enum import Enum
18
+
19
+
20
+ class BusinessCaseType(Enum):
21
+ """Standard business case types for enterprise scenarios."""
22
+ COST_OPTIMIZATION = "cost_optimization"
23
+ RESOURCE_CLEANUP = "resource_cleanup"
24
+ COMPLIANCE_FRAMEWORK = "compliance_framework"
25
+ SECURITY_ENHANCEMENT = "security_enhancement"
26
+ AUTOMATION_DEPLOYMENT = "automation_deployment"
27
+
28
+
29
+ @dataclass
30
+ class BusinessScenario:
31
+ """Dynamic business scenario configuration."""
32
+ scenario_id: str
33
+ display_name: str
34
+ business_case_type: BusinessCaseType
35
+ target_savings_min: Optional[float] = None
36
+ target_savings_max: Optional[float] = None
37
+ business_description: str = ""
38
+ technical_focus: str = ""
39
+ risk_level: str = "Medium"
40
+ implementation_status: str = "Analysis"
41
+ cli_command_suffix: str = ""
42
+
43
+ @property
44
+ def scenario_display_id(self) -> str:
45
+ """Generate enterprise-friendly scenario display ID."""
46
+ return f"{self.business_case_type.value.replace('_', '-').title()}-{self.scenario_id}"
47
+
48
+ @property
49
+ def savings_range_display(self) -> str:
50
+ """Generate savings range display for business presentations."""
51
+ if self.target_savings_min and self.target_savings_max:
52
+ if self.target_savings_min == self.target_savings_max:
53
+ return f"${self.target_savings_min:,.0f}/year"
54
+ else:
55
+ return f"${self.target_savings_min:,.0f}-${self.target_savings_max:,.0f}/year"
56
+ elif self.target_savings_min:
57
+ return f"${self.target_savings_min:,.0f}+/year"
58
+ else:
59
+ return "Analysis pending"
60
+
61
+
62
+ class BusinessCaseConfigManager:
63
+ """Enterprise business case configuration manager."""
64
+
65
+ def __init__(self, config_source: Optional[str] = None):
66
+ """
67
+ Initialize business case configuration manager.
68
+
69
+ Args:
70
+ config_source: Optional path to configuration file or environment variable prefix
71
+ """
72
+ self.config_source = config_source or "RUNBOOKS_BUSINESS_CASE"
73
+ self.scenarios = self._load_default_scenarios()
74
+ self._load_environment_overrides()
75
+
76
+ def _load_default_scenarios(self) -> Dict[str, BusinessScenario]:
77
+ """Load default enterprise business scenarios."""
78
+ return {
79
+ "workspaces": BusinessScenario(
80
+ scenario_id="workspaces",
81
+ display_name="WorkSpaces Resource Optimization",
82
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
83
+ target_savings_min=12000,
84
+ target_savings_max=15000,
85
+ business_description="Identify and optimize unused Amazon WorkSpaces for cost efficiency",
86
+ technical_focus="Zero-usage WorkSpaces detection and cost analysis",
87
+ risk_level="Low",
88
+ cli_command_suffix="workspaces"
89
+ ),
90
+ "rds-snapshots": BusinessScenario(
91
+ scenario_id="rds-snapshots",
92
+ display_name="RDS Storage Optimization",
93
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
94
+ target_savings_min=5000,
95
+ target_savings_max=24000,
96
+ business_description="Optimize manual RDS snapshots to reduce storage costs",
97
+ technical_focus="Manual RDS snapshot lifecycle management",
98
+ risk_level="Medium",
99
+ cli_command_suffix="snapshots"
100
+ ),
101
+ "backup-investigation": BusinessScenario(
102
+ scenario_id="backup-investigation",
103
+ display_name="Backup Infrastructure Analysis",
104
+ business_case_type=BusinessCaseType.COMPLIANCE_FRAMEWORK,
105
+ business_description="Investigate backup account utilization and optimization opportunities",
106
+ technical_focus="Backup infrastructure resource utilization analysis",
107
+ risk_level="Medium",
108
+ implementation_status="Framework",
109
+ cli_command_suffix="commvault"
110
+ ),
111
+ "nat-gateway": BusinessScenario(
112
+ scenario_id="nat-gateway",
113
+ display_name="Network Gateway Optimization",
114
+ business_case_type=BusinessCaseType.COST_OPTIMIZATION,
115
+ target_savings_min=8000,
116
+ target_savings_max=12000,
117
+ business_description="Optimize NAT Gateway configurations for cost efficiency",
118
+ technical_focus="NAT Gateway usage analysis and rightsizing",
119
+ cli_command_suffix="nat-gateway"
120
+ ),
121
+ "elastic-ip": BusinessScenario(
122
+ scenario_id="elastic-ip",
123
+ display_name="IP Address Resource Management",
124
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
125
+ target_savings_min=44, # $3.65 * 12 months
126
+ business_description="Optimize unattached Elastic IP addresses",
127
+ technical_focus="Elastic IP attachment analysis and cleanup recommendations",
128
+ risk_level="Low",
129
+ cli_command_suffix="elastic-ip"
130
+ ),
131
+ "ebs-optimization": BusinessScenario(
132
+ scenario_id="ebs-optimization",
133
+ display_name="Storage Volume Optimization",
134
+ business_case_type=BusinessCaseType.COST_OPTIMIZATION,
135
+ business_description="Optimize EBS volume types and utilization for cost efficiency",
136
+ technical_focus="EBS volume rightsizing and type optimization (15-20% potential)",
137
+ cli_command_suffix="ebs"
138
+ ),
139
+ "vpc-cleanup": BusinessScenario(
140
+ scenario_id="vpc-cleanup",
141
+ display_name="Network Infrastructure Cleanup",
142
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
143
+ target_savings_min=5869,
144
+ business_description="Clean up unused VPC resources and infrastructure",
145
+ technical_focus="VPC resource utilization analysis and cleanup recommendations",
146
+ cli_command_suffix="vpc-cleanup"
147
+ )
148
+ }
149
+
150
+ def _load_environment_overrides(self) -> None:
151
+ """Load configuration overrides from environment variables."""
152
+ prefix = f"{self.config_source}_"
153
+
154
+ for scenario_key, scenario in self.scenarios.items():
155
+ # Check for scenario-specific overrides
156
+ env_key = f"{prefix}{scenario_key.upper().replace('-', '_')}"
157
+
158
+ # Override target savings if specified
159
+ min_savings = os.getenv(f"{env_key}_MIN_SAVINGS")
160
+ max_savings = os.getenv(f"{env_key}_MAX_SAVINGS")
161
+
162
+ if min_savings:
163
+ scenario.target_savings_min = float(min_savings)
164
+ if max_savings:
165
+ scenario.target_savings_max = float(max_savings)
166
+
167
+ # Override display name if specified
168
+ display_name = os.getenv(f"{env_key}_DISPLAY_NAME")
169
+ if display_name:
170
+ scenario.display_name = display_name
171
+
172
+ # Override business description if specified
173
+ description = os.getenv(f"{env_key}_DESCRIPTION")
174
+ if description:
175
+ scenario.business_description = description
176
+
177
+ def get_scenario(self, scenario_key: str) -> Optional[BusinessScenario]:
178
+ """Get business scenario by key."""
179
+ return self.scenarios.get(scenario_key)
180
+
181
+ def get_all_scenarios(self) -> Dict[str, BusinessScenario]:
182
+ """Get all configured business scenarios."""
183
+ return self.scenarios
184
+
185
+ def get_scenario_choices(self) -> List[str]:
186
+ """Get list of valid scenario keys for CLI choice options."""
187
+ return list(self.scenarios.keys())
188
+
189
+ def get_scenario_help_text(self) -> str:
190
+ """Generate help text for CLI scenario option."""
191
+ help_parts = []
192
+ for key, scenario in self.scenarios.items():
193
+ savings_display = scenario.savings_range_display
194
+ help_parts.append(f"{key} ({scenario.display_name}: {savings_display})")
195
+ return "Business scenario analysis: " + ", ".join(help_parts)
196
+
197
+ def format_scenario_for_display(self, scenario_key: str,
198
+ achieved_savings: Optional[float] = None,
199
+ achievement_percentage: Optional[float] = None) -> str:
200
+ """Format scenario for display in tables and reports."""
201
+ scenario = self.get_scenario(scenario_key)
202
+ if not scenario:
203
+ return f"Unknown scenario: {scenario_key}"
204
+
205
+ base_info = f"{scenario.display_name} ({scenario.savings_range_display})"
206
+
207
+ if achieved_savings:
208
+ base_info += f" - Achieved: ${achieved_savings:,.0f}"
209
+
210
+ if achievement_percentage:
211
+ base_info += f" ({achievement_percentage:.0f}% of target)"
212
+
213
+ return base_info
214
+
215
+ def create_business_case_summary(self) -> Dict[str, Any]:
216
+ """Create executive summary of all business cases."""
217
+ total_min_savings = sum(
218
+ scenario.target_savings_min or 0
219
+ for scenario in self.scenarios.values()
220
+ )
221
+
222
+ total_max_savings = sum(
223
+ scenario.target_savings_max or 0
224
+ for scenario in self.scenarios.values()
225
+ if scenario.target_savings_max
226
+ )
227
+
228
+ return {
229
+ "total_scenarios": len(self.scenarios),
230
+ "total_potential_min": total_min_savings,
231
+ "total_potential_max": total_max_savings,
232
+ "potential_range": f"${total_min_savings:,.0f}-${total_max_savings:,.0f}",
233
+ "scenarios_by_type": {
234
+ case_type.value: [
235
+ s.display_name for s in self.scenarios.values()
236
+ if s.business_case_type == case_type
237
+ ]
238
+ for case_type in BusinessCaseType
239
+ }
240
+ }
241
+
242
+
243
+ # Global configuration manager instance
244
+ _config_manager = None
245
+
246
+ def get_business_case_config() -> BusinessCaseConfigManager:
247
+ """Get global business case configuration manager."""
248
+ global _config_manager
249
+ if _config_manager is None:
250
+ _config_manager = BusinessCaseConfigManager()
251
+ return _config_manager
252
+
253
+
254
+ def get_scenario_display_name(scenario_key: str) -> str:
255
+ """Get enterprise-friendly display name for scenario."""
256
+ config = get_business_case_config()
257
+ scenario = config.get_scenario(scenario_key)
258
+ return scenario.display_name if scenario else scenario_key.title()
259
+
260
+
261
+ def get_scenario_savings_range(scenario_key: str) -> str:
262
+ """Get savings range display for scenario."""
263
+ config = get_business_case_config()
264
+ scenario = config.get_scenario(scenario_key)
265
+ return scenario.savings_range_display if scenario else "Analysis pending"
266
+
267
+
268
+ def format_business_achievement(scenario_key: str, achieved_savings: float) -> str:
269
+ """Format business achievement for executive reporting."""
270
+ config = get_business_case_config()
271
+ scenario = config.get_scenario(scenario_key)
272
+
273
+ if not scenario:
274
+ return f"{scenario_key}: ${achieved_savings:,.0f} annual savings"
275
+
276
+ # Calculate achievement percentage if target is available
277
+ achievement_text = f"{scenario.display_name}: ${achieved_savings:,.0f} annual savings"
278
+
279
+ if scenario.target_savings_min:
280
+ percentage = (achieved_savings / scenario.target_savings_min) * 100
281
+ achievement_text += f" ({percentage:.0f}% of target)"
282
+
283
+ return achievement_text
284
+
285
+
286
+ # Migration helper functions for existing hardcoded patterns
287
+ def migrate_legacy_scenario_reference(legacy_ref: str) -> str:
288
+ """
289
+ Migrate legacy JIRA references to dynamic business case keys.
290
+
291
+ Args:
292
+ legacy_ref: Legacy reference like "FinOps-24", "finops-23", etc.
293
+
294
+ Returns:
295
+ Dynamic business case key
296
+ """
297
+ legacy_mapping = {
298
+ "finops-24": "workspaces",
299
+ "FinOps-24": "workspaces",
300
+ "finops-23": "rds-snapshots",
301
+ "FinOps-23": "rds-snapshots",
302
+ "finops-25": "backup-investigation",
303
+ "FinOps-25": "backup-investigation",
304
+ "finops-26": "nat-gateway",
305
+ "FinOps-26": "nat-gateway",
306
+ "finops-eip": "elastic-ip",
307
+ "FinOps-EIP": "elastic-ip",
308
+ "finops-ebs": "ebs-optimization",
309
+ "FinOps-EBS": "ebs-optimization",
310
+ "awso-05": "vpc-cleanup",
311
+ "AWSO-05": "vpc-cleanup"
312
+ }
313
+
314
+ return legacy_mapping.get(legacy_ref, legacy_ref.lower())
@@ -684,18 +684,33 @@ def get_cost_data(
684
684
  if amount > 0.001: # Filter out negligible costs
685
685
  costs_by_service[service] = amount
686
686
 
687
- # Calculate period metadata for trend context
687
+ # Calculate period metadata for trend context with enhanced accuracy assessment
688
688
  current_period_days = (end_date - start_date).days
689
689
  previous_period_days = (previous_period_end - previous_period_start).days
690
- is_partial_comparison = abs(current_period_days - previous_period_days) > 5
690
+ days_difference = abs(current_period_days - previous_period_days)
691
+ is_partial_comparison = days_difference > 5
692
+
693
+ # ENHANCED RELIABILITY ASSESSMENT: Consider MCP validation success in trend reliability
694
+ trend_reliability = "high"
695
+ if is_partial_comparison:
696
+ if days_difference > 15:
697
+ trend_reliability = "low"
698
+ elif days_difference > 10:
699
+ trend_reliability = "medium"
700
+ else:
701
+ # Moderate difference - reliability depends on validation accuracy
702
+ trend_reliability = "medium_with_validation_support"
691
703
 
692
704
  # Enhanced period information for trend analysis
693
705
  period_metadata = {
694
706
  "current_days": current_period_days,
695
707
  "previous_days": previous_period_days,
708
+ "days_difference": days_difference,
696
709
  "is_partial_comparison": is_partial_comparison,
697
- "comparison_type": "partial_vs_partial" if is_partial_comparison else "equal_periods",
698
- "trend_reliability": "low" if is_partial_comparison and abs(current_period_days - previous_period_days) > 10 else "high"
710
+ "comparison_type": "equal_day_comparison" if is_partial_month else "standard_month_comparison",
711
+ "trend_reliability": trend_reliability,
712
+ "period_alignment_strategy": "equal_days" if is_partial_month and days_into_month > 1 else "standard_monthly",
713
+ "supports_mcp_validation": True # This data structure supports MCP cross-validation
699
714
  }
700
715
 
701
716
  return {
@@ -132,38 +132,57 @@ def estimate_resource_costs(session: boto3.Session, regions: List[str]) -> Dict[
132
132
  }
133
133
 
134
134
  try:
135
- # EC2 Instance cost estimation with performance optimization
135
+ # EC2 Instance cost estimation using dynamic AWS pricing
136
136
  profile_name = session.profile_name if hasattr(session, "profile_name") else None
137
137
  ec2_data = ec2_summary(session, regions, profile_name)
138
+
139
+ from ..common.aws_pricing import get_ec2_monthly_cost, get_aws_pricing_engine
140
+ from ..common.rich_utils import console
141
+
138
142
  for instance_type, count in ec2_data.items():
139
143
  if count > 0:
140
- # Estimate monthly cost based on instance type
141
- # Using approximate AWS pricing (simplified model)
142
- hourly_rates = {
143
- "t3.nano": 0.0052,
144
- "t3.micro": 0.0104,
145
- "t3.small": 0.0208,
146
- "t3.medium": 0.0416,
147
- "t3.large": 0.0832,
148
- "t3.xlarge": 0.1664,
149
- "t2.nano": 0.0058,
150
- "t2.micro": 0.0116,
151
- "t2.small": 0.023,
152
- "m5.large": 0.096,
153
- "m5.xlarge": 0.192,
154
- "m5.2xlarge": 0.384,
155
- "c5.large": 0.085,
156
- "c5.xlarge": 0.17,
157
- "c5.2xlarge": 0.34,
158
- "r5.large": 0.126,
159
- "r5.xlarge": 0.252,
160
- "r5.2xlarge": 0.504,
161
- }
162
-
163
- base_type = instance_type.lower()
164
- hourly_rate = hourly_rates.get(base_type, 0.05) # Default rate
165
- monthly_cost = hourly_rate * 24 * 30 * count # Hours * days * instances
166
- estimated_costs["EC2-Instance"] += monthly_cost
144
+ try:
145
+ # Use dynamic AWS pricing - NO hardcoded values
146
+ # Assume primary region for cost estimation
147
+ primary_region = regions[0] if regions else "us-east-1"
148
+ monthly_cost_per_instance = get_ec2_monthly_cost(instance_type, primary_region)
149
+ total_monthly_cost = monthly_cost_per_instance * count
150
+ estimated_costs["EC2-Instance"] += total_monthly_cost
151
+
152
+ console.print(
153
+ f"[dim]Dynamic pricing: {count}x {instance_type} = "
154
+ f"${total_monthly_cost:.2f}/month[/]"
155
+ )
156
+
157
+ except Exception as e:
158
+ console.print(
159
+ f"[yellow]⚠ Warning: Could not get dynamic pricing for {instance_type}: {e}[/yellow]"
160
+ )
161
+
162
+ try:
163
+ # Use fallback pricing engine with AWS patterns
164
+ pricing_engine = get_aws_pricing_engine(enable_fallback=True)
165
+ primary_region = regions[0] if regions else "us-east-1"
166
+ result = pricing_engine.get_ec2_instance_pricing(instance_type, primary_region)
167
+ total_monthly_cost = result.monthly_cost * count
168
+ estimated_costs["EC2-Instance"] += total_monthly_cost
169
+
170
+ console.print(
171
+ f"[dim]Fallback pricing: {count}x {instance_type} = "
172
+ f"${total_monthly_cost:.2f}/month[/]"
173
+ )
174
+
175
+ except Exception as fallback_error:
176
+ console.print(
177
+ f"[red]⚠ ERROR: All pricing methods failed for {instance_type}: {fallback_error}[/red]"
178
+ )
179
+ console.print(
180
+ f"[red]Skipping cost estimation for {count}x {instance_type}[/red]"
181
+ )
182
+ logger.error(
183
+ f"ENTERPRISE VIOLATION: Cannot estimate cost for {instance_type} "
184
+ f"without hardcoded values. Instance type skipped."
185
+ )
167
186
 
168
187
  # Add some EC2-Other costs (EBS, snapshots, etc.)
169
188
  estimated_costs["EC2-Other"] = estimated_costs["EC2-Instance"] * 0.3
@@ -165,7 +165,7 @@ class EBSCostOptimizer:
165
165
  region_task = progress.add_task("Analyzing regions...", total=len(regions))
166
166
 
167
167
  for region in regions:
168
- print(f"🔍 Analyzing EBS volumes in {region}")
168
+ console.print(f"🔍 Analyzing EBS volumes in {region}")
169
169
 
170
170
  # Discover volumes in region
171
171
  volumes_data = self._discover_ebs_volumes(region)
@@ -166,15 +166,8 @@ class EBSOptimizer:
166
166
  profile_name=get_profile_for_operation("operational", profile_name)
167
167
  )
168
168
 
169
- # EBS pricing (per GB per month, as of 2024)
170
- self.ebs_pricing = {
171
- 'gp2': 0.10, # $0.10/GB/month
172
- 'gp3': 0.08, # $0.08/GB/month (20% cheaper than GP2)
173
- 'io1': 0.125, # $0.125/GB/month
174
- 'io2': 0.125, # $0.125/GB/month
175
- 'st1': 0.045, # $0.045/GB/month
176
- 'sc1': 0.025, # $0.025/GB/month
177
- }
169
+ # EBS pricing using dynamic AWS pricing engine for universal compatibility
170
+ self.ebs_pricing = self._initialize_dynamic_ebs_pricing()
178
171
 
179
172
  # GP3 conversion savings percentage
180
173
  self.gp3_savings_percentage = 0.20 # 20% savings GP2→GP3
@@ -184,6 +177,60 @@ class EBSOptimizer:
184
177
  self.low_usage_threshold_bytes = 1_000_000 # 1MB per day
185
178
  self.analysis_period_days = 7
186
179
 
180
+ def _initialize_dynamic_ebs_pricing(self) -> Dict[str, float]:
181
+ """Initialize dynamic EBS pricing using AWS pricing engine for universal compatibility."""
182
+ try:
183
+ from ..common.aws_pricing import get_service_monthly_cost
184
+
185
+ # Get dynamic pricing for common EBS volume types in us-east-1 (base region)
186
+ base_region = "us-east-1"
187
+
188
+ return {
189
+ 'gp2': get_service_monthly_cost("ebs_gp2", base_region, self.profile_name),
190
+ 'gp3': get_service_monthly_cost("ebs_gp3", base_region, self.profile_name),
191
+ 'io1': get_service_monthly_cost("ebs_io1", base_region, self.profile_name),
192
+ 'io2': get_service_monthly_cost("ebs_io2", base_region, self.profile_name),
193
+ 'st1': get_service_monthly_cost("ebs_st1", base_region, self.profile_name),
194
+ 'sc1': get_service_monthly_cost("ebs_sc1", base_region, self.profile_name),
195
+ }
196
+ except Exception as e:
197
+ print_warning(f"Dynamic EBS pricing initialization failed: {e}")
198
+ print_warning("Attempting AWS Pricing API fallback with universal profile support")
199
+
200
+ try:
201
+ from ..common.aws_pricing import get_aws_pricing_engine
202
+
203
+ # Use AWS Pricing API with profile support for universal compatibility
204
+ pricing_engine = get_aws_pricing_engine(profile=self.profile_name, enable_fallback=True)
205
+
206
+ # Get actual AWS pricing instead of hardcoded values
207
+ gp2_pricing = pricing_engine.get_ebs_pricing("gp2", "us-east-1")
208
+ gp3_pricing = pricing_engine.get_ebs_pricing("gp3", "us-east-1")
209
+ io1_pricing = pricing_engine.get_ebs_pricing("io1", "us-east-1")
210
+ io2_pricing = pricing_engine.get_ebs_pricing("io2", "us-east-1")
211
+ st1_pricing = pricing_engine.get_ebs_pricing("st1", "us-east-1")
212
+ sc1_pricing = pricing_engine.get_ebs_pricing("sc1", "us-east-1")
213
+
214
+ return {
215
+ 'gp2': gp2_pricing.monthly_cost_per_gb,
216
+ 'gp3': gp3_pricing.monthly_cost_per_gb,
217
+ 'io1': io1_pricing.monthly_cost_per_gb,
218
+ 'io2': io2_pricing.monthly_cost_per_gb,
219
+ 'st1': st1_pricing.monthly_cost_per_gb,
220
+ 'sc1': sc1_pricing.monthly_cost_per_gb,
221
+ }
222
+
223
+ except Exception as pricing_error:
224
+ print_error(f"ENTERPRISE COMPLIANCE VIOLATION: Cannot determine EBS pricing without AWS API access: {pricing_error}")
225
+ print_warning("Universal compatibility requires dynamic pricing - hardcoded values not permitted")
226
+
227
+ # Return error state instead of hardcoded values to maintain enterprise compliance
228
+ raise RuntimeError(
229
+ "Universal compatibility mode requires dynamic AWS pricing API access. "
230
+ "Please ensure your AWS profile has pricing:GetProducts permissions or configure "
231
+ "appropriate billing/management profile access."
232
+ )
233
+
187
234
  async def analyze_ebs_volumes(self, dry_run: bool = True) -> EBSOptimizerResults:
188
235
  """
189
236
  Comprehensive EBS volume cost optimization analysis.