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
@@ -108,7 +108,6 @@ class NetworkingCostHeatMapEngine:
108
108
  # Heat map data storage
109
109
  self.heat_map_data = {}
110
110
 
111
-
112
111
  def _initialize_aws_sessions(self):
113
112
  """Initialize AWS sessions for all profiles"""
114
113
  profiles = {
@@ -186,7 +185,7 @@ class NetworkingCostHeatMapEngine:
186
185
  logger.info("Generating single account heat map")
187
186
 
188
187
  # Use dynamic account ID resolution for universal compatibility
189
- profile_manager = AWSProfileManager(self.config.profile if hasattr(self.config, 'profile') else None)
188
+ profile_manager = AWSProfileManager(self.config.profile if hasattr(self.config, "profile") else None)
190
189
  account_id = profile_manager.get_account_id()
191
190
 
192
191
  # Create cost distribution matrix
@@ -242,10 +241,22 @@ class NetworkingCostHeatMapEngine:
242
241
 
243
242
  # Account categories with dynamic environment configuration
244
243
  account_categories = {
245
- "production": {"count": int(os.getenv("AWS_PROD_ACCOUNTS", "15")), "cost_multiplier": float(os.getenv("PROD_COST_MULTIPLIER", "5.0"))},
246
- "staging": {"count": int(os.getenv("AWS_STAGING_ACCOUNTS", "15")), "cost_multiplier": float(os.getenv("STAGING_COST_MULTIPLIER", "2.0"))},
247
- "development": {"count": int(os.getenv("AWS_DEV_ACCOUNTS", "20")), "cost_multiplier": float(os.getenv("DEV_COST_MULTIPLIER", "1.0"))},
248
- "sandbox": {"count": int(os.getenv("AWS_SANDBOX_ACCOUNTS", "10")), "cost_multiplier": float(os.getenv("SANDBOX_COST_MULTIPLIER", "0.3"))},
244
+ "production": {
245
+ "count": int(os.getenv("AWS_PROD_ACCOUNTS", "15")),
246
+ "cost_multiplier": float(os.getenv("PROD_COST_MULTIPLIER", "5.0")),
247
+ },
248
+ "staging": {
249
+ "count": int(os.getenv("AWS_STAGING_ACCOUNTS", "15")),
250
+ "cost_multiplier": float(os.getenv("STAGING_COST_MULTIPLIER", "2.0")),
251
+ },
252
+ "development": {
253
+ "count": int(os.getenv("AWS_DEV_ACCOUNTS", "20")),
254
+ "cost_multiplier": float(os.getenv("DEV_COST_MULTIPLIER", "1.0")),
255
+ },
256
+ "sandbox": {
257
+ "count": int(os.getenv("AWS_SANDBOX_ACCOUNTS", "10")),
258
+ "cost_multiplier": float(os.getenv("SANDBOX_COST_MULTIPLIER", "0.3")),
259
+ },
249
260
  }
250
261
 
251
262
  # Generate aggregated matrix
@@ -253,7 +264,7 @@ class NetworkingCostHeatMapEngine:
253
264
  account_breakdown = []
254
265
 
255
266
  # Dynamic base account ID from current AWS credentials
256
- profile_manager = AWSProfileManager(self.config.profile if hasattr(self.config, 'profile') else None)
267
+ profile_manager = AWSProfileManager(self.config.profile if hasattr(self.config, "profile") else None)
257
268
  account_id = int(profile_manager.get_account_id())
258
269
 
259
270
  for category, details in account_categories.items():
@@ -272,7 +283,9 @@ class NetworkingCostHeatMapEngine:
272
283
  "category": category,
273
284
  "monthly_cost": float(np.sum(account_matrix)),
274
285
  "primary_region": self.config.regions[int(np.argmax(np.sum(account_matrix, axis=1)))],
275
- "top_service": list(NETWORKING_SERVICES.keys())[int(np.argmax(np.sum(account_matrix, axis=0)))],
286
+ "top_service": list(NETWORKING_SERVICES.keys())[
287
+ int(np.argmax(np.sum(account_matrix, axis=0)))
288
+ ],
276
289
  }
277
290
  )
278
291
 
@@ -428,7 +441,7 @@ class NetworkingCostHeatMapEngine:
428
441
 
429
442
  for region in self.config.regions:
430
443
  # Dynamic cost calculation with real AWS Cost Explorer integration
431
- if hasattr(self, 'cost_engine') and self.cost_engine and self.cost_explorer_available:
444
+ if hasattr(self, "cost_engine") and self.cost_engine and self.cost_explorer_available:
432
445
  # Real AWS Cost Explorer data
433
446
  base_cost = self.cost_engine.get_service_cost(service_key, region)
434
447
  else:
@@ -540,14 +553,14 @@ class NetworkingCostHeatMapEngine:
540
553
 
541
554
  # Apply costs based on pattern using dynamic pricing (NO hardcoded fallbacks)
542
555
  region = self.config.regions[0] if self.config.regions else "us-east-1" # Use first configured region
543
-
556
+
544
557
  # Get dynamic service pricing
545
558
  service_pricing = self._get_dynamic_service_pricing(region)
546
-
559
+
547
560
  for service_idx, service_key in enumerate(NETWORKING_SERVICES.keys()):
548
561
  for region_idx in range(len(self.config.regions)):
549
562
  cost = 0.0 # Default to free
550
-
563
+
551
564
  # Only apply costs if we have valid pricing data
552
565
  if service_key in service_pricing and service_pricing[service_key] > 0:
553
566
  if service_key == "nat_gateway" and region_idx < pattern["nat_gateways"]:
@@ -558,7 +571,7 @@ class NetworkingCostHeatMapEngine:
558
571
  cost = service_pricing[service_key] * multiplier
559
572
  elif service_key == "elastic_ip" and region_idx < pattern.get("elastic_ips", 0):
560
573
  cost = service_pricing[service_key] * multiplier
561
-
574
+
562
575
  matrix[region_idx, service_idx] = cost
563
576
 
564
577
  return matrix
@@ -610,7 +623,7 @@ class NetworkingCostHeatMapEngine:
610
623
  def _calculate_dynamic_baseline_cost(self, service_key: str, region: str) -> float:
611
624
  """
612
625
  Calculate dynamic baseline costs using AWS pricing patterns and region multipliers.
613
-
626
+
614
627
  This replaces hardcoded values with calculation based on:
615
628
  - AWS pricing calculator patterns
616
629
  - Regional pricing differences
@@ -618,66 +631,68 @@ class NetworkingCostHeatMapEngine:
618
631
  """
619
632
  # Regional cost multipliers based on AWS pricing
620
633
  regional_multipliers = {
621
- "us-east-1": 1.0, # Base region (N. Virginia)
622
- "us-west-2": 1.05, # Oregon - slight premium
623
- "us-west-1": 1.15, # N. California - higher cost
624
- "eu-west-1": 1.10, # Ireland - EU pricing
634
+ "us-east-1": 1.0, # Base region (N. Virginia)
635
+ "us-west-2": 1.05, # Oregon - slight premium
636
+ "us-west-1": 1.15, # N. California - higher cost
637
+ "eu-west-1": 1.10, # Ireland - EU pricing
625
638
  "eu-central-1": 1.12, # Frankfurt - slightly higher
626
- "eu-west-2": 1.08, # London - competitive EU pricing
627
- "ap-southeast-1": 1.18, # Singapore - APAC premium
628
- "ap-southeast-2": 1.16, # Sydney - competitive APAC
629
- "ap-northeast-1": 1.20, # Tokyo - highest APAC
639
+ "eu-west-2": 1.08, # London - competitive EU pricing
640
+ "ap-southeast-1": 1.18, # Singapore - APAC premium
641
+ "ap-southeast-2": 1.16, # Sydney - competitive APAC
642
+ "ap-northeast-1": 1.20, # Tokyo - highest APAC
630
643
  }
631
-
644
+
632
645
  # AWS service pricing patterns (monthly USD) - DYNAMIC PRICING REQUIRED
633
646
  # ENTERPRISE COMPLIANCE: All pricing must be fetched from AWS Pricing API
634
647
  service_base_costs = self._get_dynamic_service_pricing(region)
635
-
648
+
636
649
  base_cost = service_base_costs.get(service_key, 0.0)
637
650
  region_multiplier = regional_multipliers.get(region, 1.0)
638
-
651
+
639
652
  return base_cost * region_multiplier
640
653
 
641
654
  def _get_dynamic_service_pricing(self, region: str) -> Dict[str, float]:
642
655
  """
643
656
  Get dynamic AWS service pricing following enterprise cascade:
644
-
645
- a. ✅ Try Runbooks API with boto3 (dynamic)
657
+
658
+ a. ✅ Try Runbooks API with boto3 (dynamic)
646
659
  b. ✅ Try MCP-Servers (dynamic) & gaps analysis with real AWS data
647
660
  → If failed, identify WHY option 'a' didn't work, then UPGRADE option 'a'
648
661
  c. ✅ Fail gracefully with user guidance (NO hardcoded fallback)
649
-
662
+
650
663
  ENTERPRISE COMPLIANCE: Zero tolerance for hardcoded pricing fallbacks.
651
-
664
+
652
665
  Args:
653
666
  region: AWS region for pricing lookup
654
-
667
+
655
668
  Returns:
656
669
  Dictionary of service pricing (monthly USD)
657
670
  """
658
671
  service_costs = {}
659
672
  pricing_errors = []
660
-
673
+
661
674
  # VPC itself is always free
662
675
  service_costs["vpc"] = 0.0
663
-
676
+
664
677
  # Step A: Try Runbooks Pricing API (Enhanced)
665
678
  console.print(f"[blue]🔄 Step A: Attempting Runbooks Pricing API for {region}[/blue]")
666
679
  try:
667
680
  from ..common.aws_pricing_api import AWSPricingAPI
668
-
681
+
669
682
  # Initialize with proper session management
670
- profile = getattr(self, 'profile', None)
683
+ profile = getattr(self, "profile", None)
671
684
  pricing_api = AWSPricingAPI(profile=profile)
672
-
685
+
673
686
  # NAT Gateway pricing (primary VPC cost component)
674
687
  try:
675
688
  service_costs["nat_gateway"] = pricing_api.get_nat_gateway_monthly_cost(region)
676
- console.print(f"[green]✅ NAT Gateway pricing: ${service_costs['nat_gateway']:.2f}/month from Runbooks API[/green]")
689
+ console.print(
690
+ f"[green]✅ NAT Gateway pricing: ${service_costs['nat_gateway']:.2f}/month from Runbooks API[/green]"
691
+ )
677
692
  except Exception as e:
678
693
  pricing_errors.append(f"NAT Gateway: {str(e)}")
679
694
  logger.warning(f"Runbooks API NAT Gateway pricing failed: {e}")
680
-
695
+
681
696
  # Try other services with existing API methods
682
697
  for service_key in ["vpc_endpoint", "transit_gateway", "elastic_ip"]:
683
698
  try:
@@ -685,20 +700,22 @@ class NetworkingCostHeatMapEngine:
685
700
  if hasattr(pricing_api, f"get_{service_key}_monthly_cost"):
686
701
  method = getattr(pricing_api, f"get_{service_key}_monthly_cost")
687
702
  service_costs[service_key] = method(region)
688
- console.print(f"[green]✅ {service_key} pricing: ${service_costs[service_key]:.2f}/month from Runbooks API[/green]")
703
+ console.print(
704
+ f"[green]✅ {service_key} pricing: ${service_costs[service_key]:.2f}/month from Runbooks API[/green]"
705
+ )
689
706
  else:
690
707
  pricing_errors.append(f"{service_key}: API method not implemented")
691
708
  except Exception as e:
692
709
  pricing_errors.append(f"{service_key}: {str(e)}")
693
-
710
+
694
711
  # Data transfer pricing (if API available)
695
712
  if "data_transfer" not in service_costs:
696
713
  pricing_errors.append("data_transfer: API method not implemented")
697
-
714
+
698
715
  except Exception as e:
699
716
  pricing_errors.append(f"Runbooks API initialization failed: {str(e)}")
700
717
  console.print(f"[yellow]⚠️ Runbooks Pricing API unavailable: {e}[/yellow]")
701
-
718
+
702
719
  # Step B: MCP Gap Analysis & Validation
703
720
  console.print(f"[blue]🔄 Step B: MCP Gap Analysis for missing pricing data[/blue]")
704
721
  try:
@@ -706,39 +723,41 @@ class NetworkingCostHeatMapEngine:
706
723
  for required_service in ["nat_gateway", "vpc_endpoint", "transit_gateway", "elastic_ip", "data_transfer"]:
707
724
  if required_service not in service_costs:
708
725
  missing_services.append(required_service)
709
-
726
+
710
727
  if missing_services:
711
728
  # Use MCP to identify why Runbooks API failed
712
729
  mcp_analysis = self._perform_mcp_pricing_gap_analysis(missing_services, region, pricing_errors)
713
-
730
+
714
731
  # Display MCP analysis results
715
732
  console.print(f"[cyan]📊 MCP Gap Analysis Results:[/cyan]")
716
733
  for service, analysis in mcp_analysis.items():
717
- if analysis.get('mcp_validated_cost'):
718
- service_costs[service] = analysis['mcp_validated_cost']
719
- console.print(f"[green]✅ {service}: ${analysis['mcp_validated_cost']:.2f}/month via MCP validation[/green]")
734
+ if analysis.get("mcp_validated_cost"):
735
+ service_costs[service] = analysis["mcp_validated_cost"]
736
+ console.print(
737
+ f"[green]✅ {service}: ${analysis['mcp_validated_cost']:.2f}/month via MCP validation[/green]"
738
+ )
720
739
  else:
721
740
  console.print(f"[yellow]⚠️ {service}: {analysis.get('gap_reason', 'Unknown gap')}[/yellow]")
722
-
741
+
723
742
  except Exception as e:
724
743
  pricing_errors.append(f"MCP gap analysis failed: {str(e)}")
725
744
  console.print(f"[yellow]⚠️ MCP gap analysis failed: {e}[/yellow]")
726
-
745
+
727
746
  # Step C: Graceful Failure with User Guidance (NO hardcoded fallback)
728
747
  missing_services = []
729
748
  for required_service in ["nat_gateway", "vpc_endpoint", "transit_gateway", "elastic_ip", "data_transfer"]:
730
749
  if required_service not in service_costs:
731
750
  missing_services.append(required_service)
732
-
751
+
733
752
  if missing_services:
734
753
  console.print(f"[red]🚫 ENTERPRISE COMPLIANCE: Cannot proceed with missing pricing data[/red]")
735
-
754
+
736
755
  # Generate comprehensive guidance
737
756
  self._provide_pricing_resolution_guidance(missing_services, pricing_errors, region)
738
-
757
+
739
758
  # Return empty dict to signal failure - DO NOT use hardcoded fallback
740
759
  return {"vpc": 0.0} # Only VPC (free) can be returned
741
-
760
+
742
761
  logger.info(f"✅ Successfully retrieved all service pricing for region: {region}")
743
762
  console.print(f"[green]✅ Complete dynamic pricing loaded for {region} - {len(service_costs)} services[/green]")
744
763
  return service_costs
@@ -746,222 +765,228 @@ class NetworkingCostHeatMapEngine:
746
765
  def _calculate_dynamic_base_daily_cost(self) -> float:
747
766
  """
748
767
  Calculate dynamic base daily cost from current pricing data.
749
-
768
+
750
769
  Returns:
751
770
  Daily cost estimate based on dynamic pricing, or 0.0 if unavailable
752
771
  """
753
772
  try:
754
773
  # Use primary region for calculation
755
774
  region = self.config.regions[0] if self.config.regions else "us-east-1"
756
-
775
+
757
776
  # Get dynamic service pricing
758
777
  service_pricing = self._get_dynamic_service_pricing(region)
759
-
778
+
760
779
  if not service_pricing or len(service_pricing) <= 1: # Only VPC (free) available
761
780
  console.print(f"[yellow]⚠️ No dynamic pricing available for daily cost calculation[/yellow]")
762
781
  return 0.0
763
-
782
+
764
783
  # Calculate daily cost from monthly costs
765
784
  monthly_total = sum(cost for cost in service_pricing.values() if cost > 0)
766
785
  daily_cost = monthly_total / 30.0 # Convert monthly to daily
767
-
768
- console.print(f"[cyan]📊 Dynamic daily cost calculated: ${daily_cost:.2f}/day from available services[/cyan]")
786
+
787
+ console.print(
788
+ f"[cyan]📊 Dynamic daily cost calculated: ${daily_cost:.2f}/day from available services[/cyan]"
789
+ )
769
790
  return daily_cost
770
-
791
+
771
792
  except Exception as e:
772
793
  logger.warning(f"Dynamic daily cost calculation failed: {e}")
773
794
  console.print(f"[yellow]⚠️ Dynamic daily cost calculation failed: {e}[/yellow]")
774
795
  return 0.0
775
796
 
776
- def _perform_mcp_pricing_gap_analysis(self, missing_services: List[str], region: str, pricing_errors: List[str]) -> Dict[str, Dict[str, Any]]:
797
+ def _perform_mcp_pricing_gap_analysis(
798
+ self, missing_services: List[str], region: str, pricing_errors: List[str]
799
+ ) -> Dict[str, Dict[str, Any]]:
777
800
  """
778
801
  Perform MCP gap analysis to identify why Runbooks API failed and validate alternatives.
779
-
802
+
780
803
  Args:
781
804
  missing_services: List of services missing pricing data
782
805
  region: AWS region for analysis
783
806
  pricing_errors: List of errors from previous attempts
784
-
807
+
785
808
  Returns:
786
809
  Dictionary of gap analysis results per service
787
810
  """
788
811
  gap_analysis = {}
789
-
812
+
790
813
  try:
791
814
  # Initialize MCP integration if available
792
815
  from ..common.mcp_integration import EnterpriseMCPIntegrator
793
-
816
+
794
817
  # Get profile for MCP integration
795
- profile = getattr(self, 'profile', None)
818
+ profile = getattr(self, "profile", None)
796
819
  mcp_integrator = EnterpriseMCPIntegrator(user_profile=profile, console_instance=console)
797
-
820
+
798
821
  console.print(f"[cyan]🔍 MCP analyzing {len(missing_services)} missing services...[/cyan]")
799
-
822
+
800
823
  for service in missing_services:
801
824
  analysis = {
802
- 'service': service,
803
- 'mcp_validated_cost': None,
804
- 'gap_reason': None,
805
- 'resolution_steps': [],
806
- 'cost_explorer_available': False
825
+ "service": service,
826
+ "mcp_validated_cost": None,
827
+ "gap_reason": None,
828
+ "resolution_steps": [],
829
+ "cost_explorer_available": False,
807
830
  }
808
-
831
+
809
832
  try:
810
833
  # Step 1: Try Cost Explorer for historical cost data
811
- if 'billing' in mcp_integrator.aws_sessions:
812
- billing_session = mcp_integrator.aws_sessions['billing']
813
- cost_client = billing_session.client('ce')
814
-
834
+ if "billing" in mcp_integrator.aws_sessions:
835
+ billing_session = mcp_integrator.aws_sessions["billing"]
836
+ cost_client = billing_session.client("ce")
837
+
815
838
  # Query for service-specific historical costs
816
839
  historical_cost = self._query_cost_explorer_for_service(cost_client, service, region)
817
-
840
+
818
841
  if historical_cost > 0:
819
- analysis['mcp_validated_cost'] = historical_cost
820
- analysis['cost_explorer_available'] = True
821
- console.print(f"[green]✅ MCP: {service} cost validated via Cost Explorer: ${historical_cost:.2f}/month[/green]")
842
+ analysis["mcp_validated_cost"] = historical_cost
843
+ analysis["cost_explorer_available"] = True
844
+ console.print(
845
+ f"[green]✅ MCP: {service} cost validated via Cost Explorer: ${historical_cost:.2f}/month[/green]"
846
+ )
822
847
  else:
823
- analysis['gap_reason'] = f"No historical cost data found in Cost Explorer for {service}"
824
-
848
+ analysis["gap_reason"] = f"No historical cost data found in Cost Explorer for {service}"
849
+
825
850
  # Step 2: Analyze why Runbooks API failed for this service
826
851
  service_errors = [err for err in pricing_errors if service in err.lower()]
827
852
  if service_errors:
828
- analysis['gap_reason'] = f"Runbooks API issue: {service_errors[0]}"
829
-
853
+ analysis["gap_reason"] = f"Runbooks API issue: {service_errors[0]}"
854
+
830
855
  # Determine resolution steps based on error pattern
831
- if 'not implemented' in service_errors[0]:
832
- analysis['resolution_steps'] = [
856
+ if "not implemented" in service_errors[0]:
857
+ analysis["resolution_steps"] = [
833
858
  f"Add get_{service}_monthly_cost() method to AWSPricingAPI class",
834
859
  f"Implement AWS Pricing API query for {service}",
835
- "Test with enterprise profiles"
860
+ "Test with enterprise profiles",
836
861
  ]
837
- elif 'permission' in service_errors[0].lower() or 'access' in service_errors[0].lower():
838
- analysis['resolution_steps'] = [
862
+ elif "permission" in service_errors[0].lower() or "access" in service_errors[0].lower():
863
+ analysis["resolution_steps"] = [
839
864
  "Add pricing:GetProducts permission to IAM policy",
840
865
  "Ensure profile has Cost Explorer access",
841
- f"Test pricing API access for {region} region"
866
+ f"Test pricing API access for {region} region",
842
867
  ]
843
-
868
+
844
869
  except Exception as e:
845
- analysis['gap_reason'] = f"MCP analysis failed: {str(e)}"
870
+ analysis["gap_reason"] = f"MCP analysis failed: {str(e)}"
846
871
  logger.warning(f"MCP gap analysis failed for {service}: {e}")
847
-
872
+
848
873
  gap_analysis[service] = analysis
849
-
874
+
850
875
  return gap_analysis
851
-
876
+
852
877
  except ImportError:
853
878
  console.print(f"[yellow]⚠️ MCP integration not available - basic gap analysis only[/yellow]")
854
879
  # Provide basic gap analysis without MCP
855
880
  for service in missing_services:
856
881
  gap_analysis[service] = {
857
- 'service': service,
858
- 'mcp_validated_cost': None,
859
- 'gap_reason': 'MCP integration not available',
860
- 'resolution_steps': [
861
- 'Install MCP dependencies',
862
- 'Configure MCP integration',
863
- 'Retry with MCP validation'
864
- ]
882
+ "service": service,
883
+ "mcp_validated_cost": None,
884
+ "gap_reason": "MCP integration not available",
885
+ "resolution_steps": [
886
+ "Install MCP dependencies",
887
+ "Configure MCP integration",
888
+ "Retry with MCP validation",
889
+ ],
865
890
  }
866
891
  return gap_analysis
867
-
892
+
868
893
  except Exception as e:
869
894
  console.print(f"[yellow]⚠️ MCP gap analysis error: {e}[/yellow]")
870
895
  # Return basic analysis on error
871
896
  for service in missing_services:
872
897
  gap_analysis[service] = {
873
- 'service': service,
874
- 'mcp_validated_cost': None,
875
- 'gap_reason': f'MCP analysis error: {str(e)}',
876
- 'resolution_steps': ['Check MCP configuration', 'Verify AWS profiles', 'Retry analysis']
898
+ "service": service,
899
+ "mcp_validated_cost": None,
900
+ "gap_reason": f"MCP analysis error: {str(e)}",
901
+ "resolution_steps": ["Check MCP configuration", "Verify AWS profiles", "Retry analysis"],
877
902
  }
878
903
  return gap_analysis
879
904
 
880
905
  def _query_cost_explorer_for_service(self, cost_client, service: str, region: str) -> float:
881
906
  """
882
907
  Query Cost Explorer for historical service costs to validate pricing.
883
-
908
+
884
909
  Args:
885
910
  cost_client: Boto3 Cost Explorer client
886
911
  service: Service key (nat_gateway, vpc_endpoint, etc.)
887
912
  region: AWS region
888
-
913
+
889
914
  Returns:
890
915
  Monthly cost estimate based on historical data
891
916
  """
892
917
  try:
893
918
  from datetime import datetime, timedelta
894
-
919
+
895
920
  # Map service keys to AWS Cost Explorer service names
896
921
  service_mapping = {
897
- 'nat_gateway': 'Amazon Virtual Private Cloud',
898
- 'vpc_endpoint': 'Amazon Virtual Private Cloud',
899
- 'transit_gateway': 'Amazon VPC',
900
- 'elastic_ip': 'Amazon Elastic Compute Cloud - Compute',
901
- 'data_transfer': 'Amazon CloudFront'
922
+ "nat_gateway": "Amazon Virtual Private Cloud",
923
+ "vpc_endpoint": "Amazon Virtual Private Cloud",
924
+ "transit_gateway": "Amazon VPC",
925
+ "elastic_ip": "Amazon Elastic Compute Cloud - Compute",
926
+ "data_transfer": "Amazon CloudFront",
902
927
  }
903
-
928
+
904
929
  aws_service_name = service_mapping.get(service)
905
930
  if not aws_service_name:
906
931
  return 0.0
907
-
932
+
908
933
  # Query last 3 months for more reliable average
909
934
  end_date = datetime.now().date()
910
935
  start_date = end_date - timedelta(days=90)
911
-
936
+
912
937
  response = cost_client.get_cost_and_usage(
913
- TimePeriod={
914
- 'Start': start_date.strftime('%Y-%m-%d'),
915
- 'End': end_date.strftime('%Y-%m-%d')
916
- },
917
- Granularity='MONTHLY',
918
- Metrics=['BlendedCost'],
919
- GroupBy=[
920
- {'Type': 'DIMENSION', 'Key': 'SERVICE'},
921
- {'Type': 'DIMENSION', 'Key': 'REGION'}
922
- ],
938
+ TimePeriod={"Start": start_date.strftime("%Y-%m-%d"), "End": end_date.strftime("%Y-%m-%d")},
939
+ Granularity="MONTHLY",
940
+ Metrics=["BlendedCost"],
941
+ GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}, {"Type": "DIMENSION", "Key": "REGION"}],
923
942
  Filter={
924
- 'And': [
925
- {'Dimensions': {'Key': 'SERVICE', 'Values': [aws_service_name]}},
926
- {'Dimensions': {'Key': 'REGION', 'Values': [region]}}
943
+ "And": [
944
+ {"Dimensions": {"Key": "SERVICE", "Values": [aws_service_name]}},
945
+ {"Dimensions": {"Key": "REGION", "Values": [region]}},
927
946
  ]
928
- }
947
+ },
929
948
  )
930
-
949
+
931
950
  total_cost = 0.0
932
951
  months_with_data = 0
933
-
934
- for result in response['ResultsByTime']:
935
- for group in result['Groups']:
936
- cost_amount = float(group['Metrics']['BlendedCost']['Amount'])
952
+
953
+ for result in response["ResultsByTime"]:
954
+ for group in result["Groups"]:
955
+ cost_amount = float(group["Metrics"]["BlendedCost"]["Amount"])
937
956
  if cost_amount > 0:
938
957
  total_cost += cost_amount
939
958
  months_with_data += 1
940
-
959
+
941
960
  # Calculate average monthly cost
942
961
  if months_with_data > 0:
943
962
  average_monthly_cost = total_cost / months_with_data
944
- console.print(f"[cyan]📊 Cost Explorer: {service} average ${average_monthly_cost:.2f}/month over {months_with_data} months[/cyan]")
963
+ console.print(
964
+ f"[cyan]📊 Cost Explorer: {service} average ${average_monthly_cost:.2f}/month over {months_with_data} months[/cyan]"
965
+ )
945
966
  return average_monthly_cost
946
-
967
+
947
968
  return 0.0
948
-
969
+
949
970
  except Exception as e:
950
971
  logger.warning(f"Cost Explorer query failed for {service}: {e}")
951
972
  return 0.0
952
973
 
953
- def _provide_pricing_resolution_guidance(self, missing_services: List[str], pricing_errors: List[str], region: str) -> None:
974
+ def _provide_pricing_resolution_guidance(
975
+ self, missing_services: List[str], pricing_errors: List[str], region: str
976
+ ) -> None:
954
977
  """
955
978
  Provide comprehensive guidance for resolving pricing issues.
956
-
979
+
957
980
  Args:
958
981
  missing_services: List of services missing pricing data
959
982
  pricing_errors: List of errors encountered
960
983
  region: AWS region being analyzed
961
984
  """
962
985
  console.print(f"\n[bold red]🚫 VPC HEAT MAP PRICING RESOLUTION REQUIRED[/bold red]")
963
- console.print(f"[red]Cannot generate accurate heat map without dynamic pricing for {len(missing_services)} services[/red]\n")
964
-
986
+ console.print(
987
+ f"[red]Cannot generate accurate heat map without dynamic pricing for {len(missing_services)} services[/red]\n"
988
+ )
989
+
965
990
  # Display comprehensive resolution steps
966
991
  resolution_panel = f"""[bold yellow]📋 ENTERPRISE RESOLUTION STEPS:[/bold yellow]
967
992
 
@@ -972,14 +997,14 @@ class NetworkingCostHeatMapEngine:
972
997
  • ce:GetDimensionValues
973
998
 
974
999
  [bold cyan]2. Runbooks API Enhancement:[/bold cyan]
975
- Missing API methods for: {', '.join(missing_services)}
1000
+ Missing API methods for: {", ".join(missing_services)}
976
1001
 
977
1002
  Add to src/runbooks/common/aws_pricing_api.py:
978
1003
  """
979
-
1004
+
980
1005
  for service in missing_services:
981
1006
  resolution_panel += f"\n • def get_{service}_monthly_cost(self, region: str) -> float"
982
-
1007
+
983
1008
  resolution_panel += f"""
984
1009
 
985
1010
  [bold cyan]3. Alternative Region Testing:[/bold cyan]
@@ -989,11 +1014,11 @@ class NetworkingCostHeatMapEngine:
989
1014
  • --region eu-west-1 (EU support)
990
1015
 
991
1016
  [bold cyan]4. Enterprise Override (Temporary):[/bold cyan]"""
992
-
1017
+
993
1018
  for service in missing_services:
994
1019
  service_upper = service.upper()
995
1020
  resolution_panel += f"\n export AWS_PRICING_OVERRIDE_{service_upper}_MONTHLY=<cost>"
996
-
1021
+
997
1022
  resolution_panel += f"""
998
1023
 
999
1024
  [bold cyan]5. MCP Server Integration:[/bold cyan]
@@ -1013,17 +1038,15 @@ class NetworkingCostHeatMapEngine:
1013
1038
 
1014
1039
  for i, error in enumerate(pricing_errors[:5], 1): # Show first 5 errors
1015
1040
  resolution_panel += f"\n {i}. {error}"
1016
-
1041
+
1017
1042
  from rich.panel import Panel
1043
+
1018
1044
  guidance_panel = Panel(
1019
- resolution_panel,
1020
- title="🔧 VPC Pricing Resolution Guide",
1021
- style="bold yellow",
1022
- expand=True
1045
+ resolution_panel, title="🔧 VPC Pricing Resolution Guide", style="bold yellow", expand=True
1023
1046
  )
1024
-
1047
+
1025
1048
  console.print(guidance_panel)
1026
-
1049
+
1027
1050
  # Specific next steps
1028
1051
  console.print(f"\n[bold green]✅ IMMEDIATE NEXT STEPS:[/bold green]")
1029
1052
  console.print(f"1. Run: aws pricing get-products --service-code AmazonVPC --region {region}")
@@ -1039,7 +1062,9 @@ class NetworkingCostHeatMapEngine:
1039
1062
  "total_monthly_spend": heat_maps["single_account_heat_map"]["total_monthly_cost"],
1040
1063
  "total_accounts": 1,
1041
1064
  "account_data": {
1042
- os.getenv("AWS_ACCOUNT_ID", "123456789012"): {"monthly_cost": heat_maps["single_account_heat_map"]["total_monthly_cost"]}
1065
+ os.getenv("AWS_ACCOUNT_ID", "123456789012"): {
1066
+ "monthly_cost": heat_maps["single_account_heat_map"]["total_monthly_cost"]
1067
+ }
1043
1068
  },
1044
1069
  }
1045
1070
  }