runbooks 1.1.4__py3-none-any.whl → 1.1.6__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 +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +135 -91
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +17 -12
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +99 -79
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +315 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +461 -454
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- runbooks-1.1.4.dist-info/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
runbooks/vpc/heatmap_engine.py
CHANGED
@@ -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,
|
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": {
|
246
|
-
|
247
|
-
|
248
|
-
|
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,
|
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())[
|
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,
|
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,
|
622
|
-
"us-west-2": 1.05,
|
623
|
-
"us-west-1": 1.15,
|
624
|
-
"eu-west-1": 1.10,
|
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,
|
627
|
-
"ap-southeast-1": 1.18,
|
628
|
-
"ap-southeast-2": 1.16,
|
629
|
-
"ap-northeast-1": 1.20,
|
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,
|
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(
|
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(
|
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(
|
718
|
-
service_costs[service] = analysis[
|
719
|
-
console.print(
|
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(
|
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(
|
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,
|
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
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
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
|
812
|
-
billing_session = mcp_integrator.aws_sessions[
|
813
|
-
cost_client = billing_session.client(
|
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[
|
820
|
-
analysis[
|
821
|
-
console.print(
|
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[
|
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[
|
829
|
-
|
853
|
+
analysis["gap_reason"] = f"Runbooks API issue: {service_errors[0]}"
|
854
|
+
|
830
855
|
# Determine resolution steps based on error pattern
|
831
|
-
if
|
832
|
-
analysis[
|
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
|
838
|
-
analysis[
|
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[
|
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
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
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
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
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
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
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
|
-
|
915
|
-
|
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
|
-
|
925
|
-
{
|
926
|
-
{
|
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[
|
935
|
-
for group in result[
|
936
|
-
cost_amount = float(group[
|
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(
|
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(
|
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(
|
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: {
|
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"): {
|
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
|
}
|