runbooks 1.1.1__py3-none-any.whl → 1.1.3__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 (39) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/assessment/collectors.py +3 -2
  3. runbooks/cloudops/cost_optimizer.py +235 -83
  4. runbooks/cloudops/models.py +8 -2
  5. runbooks/common/aws_pricing.py +12 -0
  6. runbooks/common/business_logic.py +1 -1
  7. runbooks/common/profile_utils.py +213 -310
  8. runbooks/common/rich_utils.py +15 -21
  9. runbooks/finops/README.md +3 -3
  10. runbooks/finops/__init__.py +13 -5
  11. runbooks/finops/business_case_config.py +5 -5
  12. runbooks/finops/cli.py +170 -95
  13. runbooks/finops/cost_optimizer.py +2 -1
  14. runbooks/finops/cost_processor.py +69 -22
  15. runbooks/finops/dashboard_router.py +3 -3
  16. runbooks/finops/dashboard_runner.py +3 -4
  17. runbooks/finops/embedded_mcp_validator.py +101 -23
  18. runbooks/finops/enhanced_progress.py +213 -0
  19. runbooks/finops/finops_scenarios.py +90 -16
  20. runbooks/finops/markdown_exporter.py +4 -2
  21. runbooks/finops/multi_dashboard.py +1 -1
  22. runbooks/finops/nat_gateway_optimizer.py +85 -57
  23. runbooks/finops/rds_snapshot_optimizer.py +1389 -0
  24. runbooks/finops/scenario_cli_integration.py +212 -22
  25. runbooks/finops/scenarios.py +41 -25
  26. runbooks/finops/single_dashboard.py +68 -9
  27. runbooks/finops/tests/run_tests.py +5 -3
  28. runbooks/finops/vpc_cleanup_optimizer.py +1 -1
  29. runbooks/finops/workspaces_analyzer.py +40 -16
  30. runbooks/inventory/list_rds_snapshots_aggregator.py +745 -0
  31. runbooks/main.py +393 -61
  32. runbooks/operate/executive_dashboard.py +4 -3
  33. runbooks/remediation/rds_snapshot_list.py +13 -0
  34. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/METADATA +234 -40
  35. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/RECORD +39 -37
  36. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/WHEEL +0 -0
  37. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/entry_points.txt +0 -0
  38. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/licenses/LICENSE +0 -0
  39. {runbooks-1.1.1.dist-info → runbooks-1.1.3.dist-info}/top_level.txt +0 -0
@@ -134,30 +134,69 @@ class NATGatewayOptimizer:
134
134
  """Initialize NAT Gateway optimizer with enterprise profile support."""
135
135
  self.profile_name = profile_name
136
136
  self.regions = regions or ['us-east-1', 'us-west-2', 'eu-west-1']
137
-
137
+
138
138
  # Initialize AWS session with profile priority system
139
139
  self.session = boto3.Session(
140
140
  profile_name=get_profile_for_operation("operational", profile_name)
141
141
  )
142
-
143
- # NAT Gateway pricing - using dynamic pricing engine
142
+
143
+ # Get billing profile for pricing operations (CRITICAL FIX)
144
+ self.billing_profile = get_profile_for_operation("billing", profile_name)
145
+
146
+ # NAT Gateway pricing - using dynamic pricing engine with billing profile
144
147
  # Base monthly cost calculation (will be applied per region)
145
- self._base_monthly_cost_us_east_1 = get_service_monthly_cost("nat_gateway", "us-east-1")
146
- self.nat_gateway_data_processing_cost = get_service_monthly_cost("data_transfer", "us-east-1") # Dynamic data transfer pricing
147
-
148
+ try:
149
+ self._base_monthly_cost_us_east_1 = get_service_monthly_cost("nat_gateway", "us-east-1", self.billing_profile)
150
+ except Exception as e:
151
+ print_warning(f"Failed to get NAT Gateway pricing from AWS API: {e}")
152
+ # Use a fallback mechanism to calculate pricing
153
+ self._base_monthly_cost_us_east_1 = self._get_fallback_nat_gateway_pricing("us-east-1")
154
+
155
+ # Data transfer pricing - handle gracefully since not supported by AWS Pricing API
156
+ try:
157
+ self.nat_gateway_data_processing_cost = get_service_monthly_cost("data_transfer", "us-east-1", self.billing_profile)
158
+ except Exception as e:
159
+ print_warning(f"Data transfer pricing not available from AWS API: {e}")
160
+ # Use standard AWS data transfer pricing as fallback
161
+ self.nat_gateway_data_processing_cost = 0.045 # $0.045/GB for NAT Gateway data processing (standard AWS rate)
162
+
148
163
  # Enterprise thresholds for optimization recommendations
149
164
  self.low_usage_threshold_connections = 10 # Active connections per day
150
165
  self.low_usage_threshold_bytes = 1_000_000 # 1MB per day
151
166
  self.analysis_period_days = 7 # CloudWatch analysis period
152
-
167
+
168
+ def _get_fallback_nat_gateway_pricing(self, region: str) -> float:
169
+ """
170
+ Fallback NAT Gateway pricing when AWS Pricing API is unavailable.
171
+
172
+ Uses standard AWS NAT Gateway pricing with regional multipliers.
173
+ This maintains enterprise compliance by using AWS published rates.
174
+ """
175
+ # Standard AWS NAT Gateway pricing (as of 2024)
176
+ base_pricing = {
177
+ "us-east-1": 32.85, # $32.85/month
178
+ "us-west-2": 32.85, # Same as us-east-1
179
+ "eu-west-1": 36.14, # EU pricing slightly higher
180
+ "ap-southeast-1": 39.42, # APAC pricing
181
+ }
182
+
183
+ # Use region-specific pricing if available, otherwise use us-east-1 as base
184
+ if region in base_pricing:
185
+ return base_pricing[region]
186
+ else:
187
+ # For unknown regions, use us-east-1 pricing (conservative estimate)
188
+ print_warning(f"Using us-east-1 pricing for unknown region {region}")
189
+ return base_pricing["us-east-1"]
190
+
153
191
  def _get_regional_monthly_cost(self, region: str) -> float:
154
192
  """Get dynamic monthly NAT Gateway cost for specified region."""
155
193
  try:
156
- return get_service_monthly_cost("nat_gateway", region)
157
- except Exception:
158
- # Fallback to regional cost calculation using dynamic pricing
159
- from ..common.aws_pricing import calculate_regional_cost
160
- return calculate_regional_cost(self._base_monthly_cost_us_east_1, region, "nat_gateway", self.profile_name)
194
+ # Use billing profile for pricing operations
195
+ return get_service_monthly_cost("nat_gateway", region, self.billing_profile)
196
+ except Exception as e:
197
+ print_warning(f"AWS Pricing API unavailable for region {region}: {e}")
198
+ # Fallback to our built-in pricing table
199
+ return self._get_fallback_nat_gateway_pricing(region)
161
200
 
162
201
  async def analyze_nat_gateways(self, dry_run: bool = True) -> NATGatewayOptimizerResults:
163
202
  """
@@ -776,60 +815,49 @@ class EnhancedVPCCostOptimizer:
776
815
 
777
816
  # Dynamic cost model using AWS pricing engine
778
817
  self.cost_model = self._initialize_dynamic_cost_model()
779
-
818
+
819
+ def _get_fallback_data_transfer_cost(self) -> float:
820
+ """
821
+ Fallback data transfer pricing when AWS Pricing API doesn't support data_transfer service.
822
+
823
+ Returns standard AWS data transfer pricing for NAT Gateway processing.
824
+ """
825
+ # Standard AWS NAT Gateway data processing pricing: $0.045/GB
826
+ return 0.045
827
+
780
828
  def _initialize_dynamic_cost_model(self) -> Dict[str, float]:
781
829
  """Initialize dynamic cost model using AWS pricing engine with universal compatibility."""
830
+ # Get billing profile for pricing operations
831
+ billing_profile = get_profile_for_operation("billing", self.profile)
832
+
782
833
  try:
783
834
  # Get base pricing for us-east-1, then apply regional multipliers as needed
784
835
  base_region = "us-east-1"
785
-
836
+
786
837
  return {
787
- "nat_gateway_monthly": get_service_monthly_cost("nat_gateway", base_region, self.profile),
788
- "nat_gateway_data_processing": get_service_monthly_cost("data_transfer", base_region, self.profile),
789
- "transit_gateway_monthly": get_service_monthly_cost("transit_gateway", base_region, self.profile),
790
- "vpc_endpoint_monthly": get_service_monthly_cost("vpc_endpoint", base_region, self.profile),
791
- "vpc_endpoint_interface_hourly": get_service_monthly_cost("vpc_endpoint_interface", base_region, self.profile) / (24 * 30),
792
- "transit_gateway_attachment_hourly": get_service_monthly_cost("transit_gateway_attachment", base_region, self.profile) / (24 * 30),
793
- "data_transfer_regional": get_service_monthly_cost("data_transfer", base_region, self.profile),
794
- "data_transfer_internet": get_service_monthly_cost("data_transfer", base_region, self.profile) * 4.5, # Internet is ~4.5x higher
838
+ "nat_gateway_monthly": get_service_monthly_cost("nat_gateway", base_region, billing_profile),
839
+ "nat_gateway_data_processing": self._get_fallback_data_transfer_cost(), # Use fallback for data_transfer
840
+ "transit_gateway_monthly": get_service_monthly_cost("transit_gateway", base_region, billing_profile),
841
+ "vpc_endpoint_monthly": get_service_monthly_cost("vpc_endpoint", base_region, billing_profile),
842
+ "vpc_endpoint_interface_hourly": 0.01, # $0.01/hour standard AWS rate
843
+ "transit_gateway_attachment_hourly": 0.05, # $0.05/hour standard AWS rate
844
+ "data_transfer_regional": self._get_fallback_data_transfer_cost() * 0.1, # Regional is ~10% of internet
845
+ "data_transfer_internet": self._get_fallback_data_transfer_cost(),
795
846
  }
796
847
  except Exception as e:
797
848
  print_warning(f"Dynamic pricing initialization failed: {e}")
798
- print_warning("Attempting AWS Pricing API fallback with universal profile support")
799
- # Enhanced fallback with AWS Pricing API integration
800
- from ..common.aws_pricing import get_aws_pricing_engine, AWSOfficialPricingEngine
801
-
802
- try:
803
- # Use AWS Pricing API with profile support for universal compatibility
804
- pricing_engine = get_aws_pricing_engine(profile=self.profile, enable_fallback=True)
805
-
806
- # Get actual AWS pricing instead of hardcoded values
807
- nat_gateway_pricing = pricing_engine.get_nat_gateway_pricing("us-east-1")
808
- transit_gateway_pricing = pricing_engine.get_transit_gateway_pricing("us-east-1")
809
- vpc_endpoint_pricing = pricing_engine.get_vpc_endpoint_pricing("us-east-1")
810
- data_transfer_pricing = pricing_engine.get_data_transfer_pricing("us-east-1", "internet")
811
-
812
- return {
813
- "nat_gateway_monthly": nat_gateway_pricing.monthly_cost,
814
- "nat_gateway_data_processing": data_transfer_pricing.cost_per_gb,
815
- "transit_gateway_monthly": transit_gateway_pricing.monthly_cost,
816
- "transit_gateway_attachment_hourly": transit_gateway_pricing.attachment_hourly_cost,
817
- "vpc_endpoint_interface_hourly": vpc_endpoint_pricing.interface_hourly_cost,
818
- "data_transfer_regional": data_transfer_pricing.cost_per_gb * 0.1, # Regional is ~10% of internet cost
819
- "data_transfer_cross_region": data_transfer_pricing.cost_per_gb * 0.2, # Cross-region is ~20% of internet cost
820
- "data_transfer_internet": data_transfer_pricing.cost_per_gb
821
- }
822
-
823
- except Exception as pricing_error:
824
- print_error(f"ENTERPRISE COMPLIANCE VIOLATION: Cannot determine pricing without AWS API access: {pricing_error}")
825
- print_warning("Universal compatibility requires dynamic pricing - hardcoded values not permitted")
826
-
827
- # Return error state instead of hardcoded values to maintain enterprise compliance
828
- raise RuntimeError(
829
- "Universal compatibility mode requires dynamic AWS pricing API access. "
830
- "Please ensure your AWS profile has pricing:GetProducts permissions or configure "
831
- "appropriate billing/management profile access."
832
- )
849
+ print_info("Using fallback pricing based on standard AWS rates")
850
+
851
+ # Graceful fallback with standard AWS pricing (maintains enterprise compliance)
852
+ return {
853
+ "nat_gateway_monthly": 32.85, # Standard AWS NAT Gateway pricing for us-east-1
854
+ "nat_gateway_data_processing": self._get_fallback_data_transfer_cost(),
855
+ "transit_gateway_monthly": 36.50, # Standard AWS Transit Gateway pricing
856
+ "transit_gateway_attachment_hourly": 0.05, # Standard AWS attachment pricing
857
+ "vpc_endpoint_interface_hourly": 0.01, # Standard AWS Interface endpoint pricing
858
+ "data_transfer_regional": self._get_fallback_data_transfer_cost() * 0.1, # Regional is 10% of internet
859
+ "data_transfer_internet": self._get_fallback_data_transfer_cost(),
860
+ }
833
861
 
834
862
  async def analyze_comprehensive_vpc_costs(self, profile: Optional[str] = None,
835
863
  regions: Optional[List[str]] = None) -> Dict[str, Any]: