runbooks 0.9.9__py3-none-any.whl → 1.0.1__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 +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
- runbooks/cfat/app.ts +27 -19
- runbooks/cfat/assessment/runner.py +6 -5
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cfat/tests/test_weight_configuration.ts +449 -0
- runbooks/cfat/weight_config.ts +574 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/__init__.py +26 -9
- runbooks/common/aws_pricing.py +1353 -0
- runbooks/common/aws_pricing_api.py +205 -0
- runbooks/common/aws_utils.py +2 -2
- runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
- runbooks/common/cross_account_manager.py +606 -0
- runbooks/common/date_utils.py +115 -0
- runbooks/common/enhanced_exception_handler.py +14 -7
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_cost_explorer_integration.py +5 -4
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +127 -72
- runbooks/common/rich_utils.py +3 -3
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/dashboard_runner.py +47 -28
- runbooks/finops/ebs_optimizer.py +56 -9
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +10 -4
- runbooks/finops/finops_dashboard.py +6 -5
- runbooks/finops/iam_guidance.py +6 -1
- runbooks/finops/markdown_exporter.py +217 -2
- runbooks/finops/nat_gateway_optimizer.py +76 -20
- runbooks/finops/tests/test_integration.py +3 -1
- runbooks/finops/vpc_cleanup_exporter.py +28 -26
- runbooks/finops/vpc_cleanup_optimizer.py +363 -16
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1177 -94
- runbooks/inventory/discovery.md +339 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +6 -9
- runbooks/inventory/list_ec2_instances.py +3 -3
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +104 -9
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1279 -0
- runbooks/inventory/verify_ec2_security_groups.py +3 -1
- runbooks/inventory/vpc_analyzer.py +825 -7
- runbooks/inventory/vpc_flow_analyzer.py +36 -42
- runbooks/main.py +708 -47
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/base.py +9 -6
- runbooks/operate/deployment_framework.py +5 -4
- runbooks/operate/deployment_validator.py +6 -5
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/mcp_integration.py +6 -5
- runbooks/operate/networking_cost_heatmap.py +21 -16
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +100 -12
- runbooks/remediation/base.py +4 -2
- runbooks/remediation/commons.py +5 -5
- runbooks/remediation/commvault_ec2_analysis.py +68 -15
- runbooks/remediation/config/accounts_example.json +31 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/multi_account.py +120 -7
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/remediation/remediation_cli.py +710 -0
- runbooks/remediation/universal_account_discovery.py +377 -0
- runbooks/security/compliance_automation_engine.py +99 -20
- runbooks/security/config/__init__.py +24 -0
- runbooks/security/config/compliance_config.py +255 -0
- runbooks/security/config/compliance_weights_example.json +22 -0
- runbooks/security/config_template_generator.py +500 -0
- runbooks/security/security_cli.py +377 -0
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/cli.py +8 -7
- runbooks/validation/comprehensive_2way_validator.py +2007 -0
- runbooks/validation/mcp_validator.py +965 -101
- runbooks/validation/terraform_citations_validator.py +363 -0
- runbooks/validation/terraform_drift_detector.py +1098 -0
- runbooks/vpc/cleanup_wrapper.py +231 -10
- runbooks/vpc/config.py +346 -73
- runbooks/vpc/cross_account_session.py +312 -0
- runbooks/vpc/heatmap_engine.py +115 -41
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1630 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks_adapter.py +33 -12
- runbooks/vpc/tests/conftest.py +4 -2
- runbooks/vpc/tests/test_cost_engine.py +4 -2
- runbooks/vpc/unified_scenarios.py +73 -3
- runbooks/vpc/vpc_cleanup_integration.py +512 -78
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/METADATA +94 -52
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/RECORD +101 -81
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,6 @@
|
|
2
2
|
EC2 Utilization Analysis - Investigate EC2 utilization patterns for cost optimization.
|
3
3
|
|
4
4
|
Business Case: Enhanced EC2 investigation framework for backup infrastructure analysis
|
5
|
-
Target Account: 637423383469 (Backup infrastructure account)
|
6
5
|
Challenge: Determine if EC2 instances are actively used for backups or idle
|
7
6
|
Strategic Value: Infrastructure right-sizing and cost optimization through utilization analysis
|
8
7
|
"""
|
@@ -19,10 +18,60 @@ from ..common.rich_utils import (
|
|
19
18
|
console, print_header, print_success, print_error, print_warning,
|
20
19
|
create_table, create_progress_bar, format_cost
|
21
20
|
)
|
21
|
+
from ..common.profile_utils import create_operational_session
|
22
22
|
|
23
23
|
logger = logging.getLogger(__name__)
|
24
24
|
|
25
25
|
|
26
|
+
def _calculate_instance_monthly_cost(instance_type: str, region: str = "us-east-1") -> float:
|
27
|
+
"""
|
28
|
+
Calculate monthly cost for EC2 instance type using dynamic AWS Pricing API.
|
29
|
+
|
30
|
+
ENTERPRISE COMPLIANCE: Uses AWS Pricing API to eliminate hardcoded pricing violations.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
instance_type: EC2 instance type (e.g., 't3.micro', 'm5.large')
|
34
|
+
region: AWS region for pricing lookup
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
float: Monthly cost in USD from AWS Pricing API
|
38
|
+
"""
|
39
|
+
from ..common.aws_pricing import get_ec2_monthly_cost
|
40
|
+
from ..common.rich_utils import console
|
41
|
+
|
42
|
+
try:
|
43
|
+
# Use dynamic AWS pricing - NO hardcoded values allowed
|
44
|
+
monthly_cost = get_ec2_monthly_cost(instance_type, region)
|
45
|
+
logger.debug(f"Dynamic pricing for {instance_type}: ${monthly_cost:.2f}/month")
|
46
|
+
return monthly_cost
|
47
|
+
|
48
|
+
except Exception as e:
|
49
|
+
console.print(f"[red]⚠ ENTERPRISE WARNING: Cannot get dynamic pricing for {instance_type}: {e}[/red]")
|
50
|
+
console.print(f"[yellow]Falling back to AWS pricing pattern calculation...[/yellow]")
|
51
|
+
|
52
|
+
# Use AWS pricing engine's fallback calculation (which uses documented AWS patterns)
|
53
|
+
from ..common.aws_pricing import get_aws_pricing_engine
|
54
|
+
|
55
|
+
try:
|
56
|
+
pricing_engine = get_aws_pricing_engine(enable_fallback=True)
|
57
|
+
result = pricing_engine.get_ec2_instance_pricing(instance_type, region)
|
58
|
+
logger.warning(f"Using fallback pricing for {instance_type}: ${result.monthly_cost:.2f}/month")
|
59
|
+
return result.monthly_cost
|
60
|
+
|
61
|
+
except Exception as fallback_error:
|
62
|
+
logger.error(f"All pricing methods failed for {instance_type}: {fallback_error}")
|
63
|
+
|
64
|
+
# Complete failure - cannot proceed without violating enterprise standards
|
65
|
+
raise RuntimeError(
|
66
|
+
f"ENTERPRISE VIOLATION: Cannot get dynamic pricing for {instance_type} "
|
67
|
+
f"in region {region}. All pricing methods failed. "
|
68
|
+
f"Hardcoded values are prohibited. "
|
69
|
+
f"Ensure AWS credentials are configured and Pricing API is accessible."
|
70
|
+
) from e
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
26
75
|
def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
|
27
76
|
"""
|
28
77
|
Calculate potential cost impact for EC2 instances.
|
@@ -33,21 +82,14 @@ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
|
|
33
82
|
running_instances = [i for i in instances_data if i.get("State", {}).get("Name") == "running"]
|
34
83
|
idle_instances = [i for i in instances_data if i.get("CpuUtilization", 0) < 5.0] # <5% CPU
|
35
84
|
|
36
|
-
#
|
85
|
+
# Dynamic cost estimation using environment configuration
|
37
86
|
estimated_monthly_cost = 0.0
|
38
87
|
for instance in running_instances:
|
39
88
|
instance_type = instance.get("InstanceType", "t3.micro")
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"t3.medium": 33.87,
|
45
|
-
"m5.large": 70.08,
|
46
|
-
"m5.xlarge": 140.16,
|
47
|
-
"c5.large": 62.98,
|
48
|
-
"c5.xlarge": 125.95,
|
49
|
-
}
|
50
|
-
estimated_monthly_cost += cost_map.get(instance_type, 50.0) # Default $50/month
|
89
|
+
|
90
|
+
# Dynamic cost calculation based on instance type
|
91
|
+
# Using AWS pricing calculation: hours * daily_hours * monthly_days
|
92
|
+
estimated_monthly_cost += _calculate_instance_monthly_cost(instance_type)
|
51
93
|
|
52
94
|
return {
|
53
95
|
"total_instances": total_instances,
|
@@ -61,7 +103,7 @@ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
|
|
61
103
|
|
62
104
|
@click.command()
|
63
105
|
@click.option("--output-file", default="./tmp/commvault_ec2_investigation.csv", help="Output CSV file path")
|
64
|
-
@click.option("--account",
|
106
|
+
@click.option("--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account.")
|
65
107
|
@click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
|
66
108
|
@click.option("--days", default=7, help="Number of days to analyze for utilization metrics")
|
67
109
|
@click.option("--dry-run", is_flag=True, default=True, help="Preview analysis without execution")
|
@@ -69,11 +111,22 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
|
|
69
111
|
"""
|
70
112
|
FinOps-25: Commvault EC2 investigation for cost optimization.
|
71
113
|
|
72
|
-
Account: 637423383469 (Commvault backup account)
|
73
114
|
Challenge: Determine if EC2 instances are actively used for backups or idle
|
74
115
|
"""
|
75
116
|
print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
|
76
117
|
|
118
|
+
# Auto-detect account if not specified
|
119
|
+
if not account:
|
120
|
+
try:
|
121
|
+
session = create_operational_session()
|
122
|
+
sts = session.client('sts')
|
123
|
+
identity = sts.get_caller_identity()
|
124
|
+
account = identity['Account']
|
125
|
+
console.print(f"[dim cyan]Auto-detected account: {account}[/dim cyan]")
|
126
|
+
except Exception as e:
|
127
|
+
console.print(f"[red]Error detecting account: {e}[/red]")
|
128
|
+
raise click.ClickException("Could not determine AWS account. Please specify --account parameter.")
|
129
|
+
|
77
130
|
account_info = display_aws_account_info()
|
78
131
|
console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
|
79
132
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"target_accounts": [
|
3
|
+
{
|
4
|
+
"account_id": "111122223333",
|
5
|
+
"environment": "production",
|
6
|
+
"name": "Production Account",
|
7
|
+
"enabled": true
|
8
|
+
},
|
9
|
+
{
|
10
|
+
"account_id": "444455556666",
|
11
|
+
"environment": "staging",
|
12
|
+
"name": "Staging Account",
|
13
|
+
"enabled": true
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"account_id": "777788889999",
|
17
|
+
"environment": "development",
|
18
|
+
"name": "Development Account",
|
19
|
+
"enabled": false
|
20
|
+
}
|
21
|
+
],
|
22
|
+
"remediation_settings": {
|
23
|
+
"parallel_execution": true,
|
24
|
+
"max_workers": 5,
|
25
|
+
"timeout_seconds": 300,
|
26
|
+
"retry_attempts": 3
|
27
|
+
},
|
28
|
+
"description": "Configuration for multi-account remediation targets",
|
29
|
+
"last_updated": "2024-12-19",
|
30
|
+
"version": "1.0"
|
31
|
+
}
|
@@ -305,9 +305,12 @@ def detect_and_delete_volumes(dry_run: bool, max_age_days: int, output_file: Opt
|
|
305
305
|
else:
|
306
306
|
days_since_detached = volume_age_days # Never attached
|
307
307
|
|
308
|
-
#
|
309
|
-
|
310
|
-
|
308
|
+
# Real-time EBS cost from AWS Pricing API - NO hardcoded defaults
|
309
|
+
from runbooks.common.aws_pricing_api import pricing_api
|
310
|
+
if volume_type == "gp3":
|
311
|
+
cost_per_gb = pricing_api.get_ebs_gp3_cost_per_gb(region_name)
|
312
|
+
else:
|
313
|
+
cost_per_gb = pricing_api.get_ebs_gp2_cost_per_gb(region_name)
|
311
314
|
monthly_cost = volume_size * cost_per_gb
|
312
315
|
total_cost_gb_month += monthly_cost
|
313
316
|
|
@@ -40,12 +40,9 @@ remediator = MultiAccountRemediator(
|
|
40
40
|
max_workers=5
|
41
41
|
)
|
42
42
|
|
43
|
-
# Define target accounts
|
44
|
-
accounts
|
45
|
-
|
46
|
-
AWSAccount("987654321098", "staging"),
|
47
|
-
AWSAccount("456789012345", "development")
|
48
|
-
]
|
43
|
+
# Define target accounts using dynamic discovery
|
44
|
+
# Example: Get accounts from AWS Organizations or environment configuration
|
45
|
+
accounts = get_accounts_from_environment() or discover_organization_accounts()
|
49
46
|
|
50
47
|
# Execute bulk S3 security remediation
|
51
48
|
results = remediator.bulk_s3_security(
|
@@ -73,6 +70,8 @@ from loguru import logger
|
|
73
70
|
|
74
71
|
from runbooks.inventory.models.account import AWSAccount
|
75
72
|
from runbooks.remediation.base import BaseRemediation, RemediationContext, RemediationResult, RemediationStatus
|
73
|
+
from runbooks.common.profile_utils import create_management_session
|
74
|
+
from runbooks.remediation.universal_account_discovery import UniversalAccountDiscovery, AWSAccount as UniversalAWSAccount
|
76
75
|
|
77
76
|
|
78
77
|
class MultiAccountRemediator:
|
@@ -167,7 +166,7 @@ class MultiAccountRemediator:
|
|
167
166
|
|
168
167
|
try:
|
169
168
|
# Create SSO OIDC client
|
170
|
-
sso_oidc = boto3.client("sso-oidc", region_name="us-east-1")
|
169
|
+
sso_oidc = boto3.client("sso-oidc", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
|
171
170
|
|
172
171
|
# Register client
|
173
172
|
client_creds = sso_oidc.register_client(clientName="CloudOpsRemediation", clientType="public")
|
@@ -567,3 +566,117 @@ class MultiAccountRemediator:
|
|
567
566
|
compliance_summary["compliance_controls"] = list(compliance_summary["compliance_controls"])
|
568
567
|
|
569
568
|
return compliance_summary
|
569
|
+
|
570
|
+
|
571
|
+
# Dynamic account discovery functions for enterprise security operations
|
572
|
+
def get_accounts_from_environment(profile: Optional[str] = None) -> Optional[List[AWSAccount]]:
|
573
|
+
"""
|
574
|
+
Get AWS accounts using universal account discovery system.
|
575
|
+
|
576
|
+
Uses enhanced discovery with support for:
|
577
|
+
- Environment variables (REMEDIATION_TARGET_ACCOUNTS)
|
578
|
+
- Configuration files (REMEDIATION_ACCOUNT_CONFIG)
|
579
|
+
- AWS Organizations API (automatic discovery)
|
580
|
+
- Current account fallback (single account mode)
|
581
|
+
|
582
|
+
Args:
|
583
|
+
profile: AWS profile to use for discovery
|
584
|
+
|
585
|
+
Returns:
|
586
|
+
List of AWSAccount objects or None if not configured
|
587
|
+
"""
|
588
|
+
try:
|
589
|
+
# Use universal account discovery system
|
590
|
+
discovery = UniversalAccountDiscovery(profile=profile)
|
591
|
+
universal_accounts = discovery.discover_target_accounts()
|
592
|
+
|
593
|
+
if not universal_accounts:
|
594
|
+
return None
|
595
|
+
|
596
|
+
# Convert to legacy AWSAccount format for compatibility
|
597
|
+
legacy_accounts = []
|
598
|
+
for universal_account in universal_accounts:
|
599
|
+
legacy_account = AWSAccount(
|
600
|
+
universal_account.account_id,
|
601
|
+
universal_account.account_name or f"account-{universal_account.account_id}"
|
602
|
+
)
|
603
|
+
legacy_accounts.append(legacy_account)
|
604
|
+
|
605
|
+
logger.info(f"Using {len(legacy_accounts)} accounts discovered via universal discovery system")
|
606
|
+
return legacy_accounts
|
607
|
+
|
608
|
+
except Exception as e:
|
609
|
+
logger.warning(f"Failed to discover accounts using universal discovery: {e}")
|
610
|
+
return None
|
611
|
+
|
612
|
+
|
613
|
+
def discover_organization_accounts(profile: Optional[str] = None) -> List[AWSAccount]:
|
614
|
+
"""
|
615
|
+
Discover AWS accounts using universal discovery system.
|
616
|
+
|
617
|
+
Enhanced to use the universal account discovery system which provides:
|
618
|
+
- Organizations API discovery (if available)
|
619
|
+
- Environment variable fallback
|
620
|
+
- Configuration file support
|
621
|
+
- Current account fallback
|
622
|
+
|
623
|
+
Args:
|
624
|
+
profile: AWS profile for discovery (universal profile management)
|
625
|
+
|
626
|
+
Returns:
|
627
|
+
List of discovered AWSAccount objects
|
628
|
+
"""
|
629
|
+
try:
|
630
|
+
# Use universal account discovery system for Organizations discovery
|
631
|
+
discovery = UniversalAccountDiscovery(profile=profile)
|
632
|
+
universal_accounts = discovery._get_accounts_from_organizations()
|
633
|
+
|
634
|
+
if not universal_accounts:
|
635
|
+
# Fallback to other discovery methods
|
636
|
+
logger.info("Organizations API not available, trying other discovery methods...")
|
637
|
+
universal_accounts = discovery.discover_target_accounts()
|
638
|
+
|
639
|
+
# Convert to legacy AWSAccount format for compatibility
|
640
|
+
legacy_accounts = []
|
641
|
+
for universal_account in universal_accounts:
|
642
|
+
if universal_account.status == "ACTIVE":
|
643
|
+
legacy_account = AWSAccount(
|
644
|
+
universal_account.account_id,
|
645
|
+
universal_account.account_name or f"org-account-{universal_account.account_id}"
|
646
|
+
)
|
647
|
+
legacy_accounts.append(legacy_account)
|
648
|
+
|
649
|
+
logger.info(f"Discovered {len(legacy_accounts)} active AWS accounts via universal discovery")
|
650
|
+
return legacy_accounts
|
651
|
+
|
652
|
+
except Exception as e:
|
653
|
+
logger.warning(f"Failed to discover organization accounts: {e}")
|
654
|
+
# Universal discovery handles all fallback scenarios
|
655
|
+
return []
|
656
|
+
|
657
|
+
|
658
|
+
def _determine_account_environment(account_name: str) -> str:
|
659
|
+
"""
|
660
|
+
Determine account environment based on account name patterns.
|
661
|
+
|
662
|
+
Args:
|
663
|
+
account_name: AWS account name
|
664
|
+
|
665
|
+
Returns:
|
666
|
+
Environment classification
|
667
|
+
"""
|
668
|
+
name_lower = account_name.lower()
|
669
|
+
|
670
|
+
# Common environment patterns
|
671
|
+
if any(env in name_lower for env in ["prod", "production"]):
|
672
|
+
return "production"
|
673
|
+
elif any(env in name_lower for env in ["staging", "stage", "uat"]):
|
674
|
+
return "staging"
|
675
|
+
elif any(env in name_lower for env in ["dev", "development"]):
|
676
|
+
return "development"
|
677
|
+
elif any(env in name_lower for env in ["test", "testing"]):
|
678
|
+
return "testing"
|
679
|
+
elif any(env in name_lower for env in ["sandbox", "sb"]):
|
680
|
+
return "sandbox"
|
681
|
+
else:
|
682
|
+
return "unknown"
|
@@ -40,9 +40,11 @@ def estimate_snapshot_cost(allocated_storage, storage_type="gp2", days_old=1):
|
|
40
40
|
JIRA FinOps-23: Enhanced cost estimation for $5K-24K annual savings target
|
41
41
|
Based on AWS RDS snapshot pricing: https://aws.amazon.com/rds/pricing/
|
42
42
|
"""
|
43
|
-
# RDS Snapshot
|
44
|
-
|
45
|
-
|
43
|
+
# Real-time RDS Snapshot cost from AWS Pricing API - NO hardcoded defaults
|
44
|
+
from runbooks.common.aws_pricing_api import pricing_api
|
45
|
+
# Get region from caller context or default to us-east-1
|
46
|
+
region = os.getenv('AWS_DEFAULT_REGION', 'us-east-1')
|
47
|
+
snapshot_cost_per_gb_month = pricing_api.get_rds_snapshot_cost_per_gb(region)
|
46
48
|
|
47
49
|
# Calculate base monthly cost
|
48
50
|
monthly_cost = allocated_storage * snapshot_cost_per_gb_month
|