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
@@ -36,456 +36,420 @@ from runbooks.operate.base import BaseOperation, OperationContext, OperationResu
|
|
36
36
|
|
37
37
|
class RDSOperations(BaseOperation):
|
38
38
|
"""AWS RDS operations for cost optimization and security compliance."""
|
39
|
-
|
39
|
+
|
40
40
|
def __init__(self, profile: Optional[str] = None, region: Optional[str] = None, dry_run: bool = True):
|
41
41
|
"""Initialize RDS operations."""
|
42
42
|
super().__init__(profile=profile, region=region, dry_run=dry_run)
|
43
|
-
self.service =
|
43
|
+
self.service = "rds"
|
44
44
|
self.supported_operations = {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
"find_low_cpu_instances",
|
46
|
+
"delete_instance_safely",
|
47
|
+
"get_publicly_accessible_instances",
|
48
|
+
"secure_public_instances",
|
49
|
+
"analyze_reserved_instance_opportunities",
|
50
|
+
"get_instance_metrics",
|
51
|
+
"list_instances",
|
52
|
+
"get_instance_details",
|
53
53
|
}
|
54
54
|
|
55
55
|
def find_low_cpu_instances(
|
56
|
-
self,
|
57
|
-
context: OperationContext,
|
58
|
-
utilization_threshold: float = 10.0,
|
59
|
-
duration_minutes: int = 60
|
56
|
+
self, context: OperationContext, utilization_threshold: float = 10.0, duration_minutes: int = 60
|
60
57
|
) -> List[OperationResult]:
|
61
58
|
"""
|
62
59
|
Find RDS instances with low CPU utilization.
|
63
|
-
|
60
|
+
|
64
61
|
Extracted from: AWS_Delete_RDS_Instances_with_Low_CPU_Utilization.ipynb
|
65
|
-
|
62
|
+
|
66
63
|
Args:
|
67
64
|
context: Operation context
|
68
65
|
utilization_threshold: CPU threshold percentage (default 10%)
|
69
66
|
duration_minutes: Duration to analyze metrics (default 60 minutes)
|
70
|
-
|
67
|
+
|
71
68
|
Returns:
|
72
69
|
List of operation results with low CPU instances
|
73
70
|
"""
|
74
71
|
print_header("RDS Low CPU Analysis", "Cost Optimization")
|
75
|
-
|
72
|
+
|
76
73
|
results = []
|
77
74
|
regions_to_check = [context.region] if context.region else self._get_all_regions()
|
78
|
-
|
75
|
+
|
79
76
|
for region in track(regions_to_check, description="Analyzing regions..."):
|
80
77
|
try:
|
81
78
|
# Get RDS client for region
|
82
79
|
session = self._get_aws_session(context.profile)
|
83
|
-
rds_client = session.client(
|
84
|
-
cloudwatch_client = session.client(
|
85
|
-
|
80
|
+
rds_client = session.client("rds", region_name=region)
|
81
|
+
cloudwatch_client = session.client("cloudwatch", region_name=region)
|
82
|
+
|
86
83
|
# Get all RDS instances in region
|
87
|
-
instances = get_paginated_results(
|
88
|
-
|
89
|
-
'describe_db_instances',
|
90
|
-
'DBInstances'
|
91
|
-
)
|
92
|
-
|
84
|
+
instances = get_paginated_results(rds_client, "describe_db_instances", "DBInstances")
|
85
|
+
|
93
86
|
region_low_cpu_instances = []
|
94
|
-
|
87
|
+
|
95
88
|
for db in instances:
|
96
89
|
try:
|
97
|
-
db_identifier = db[
|
98
|
-
|
90
|
+
db_identifier = db["DBInstanceIdentifier"]
|
91
|
+
|
99
92
|
# Get CPU metrics from CloudWatch
|
100
93
|
end_time = datetime.utcnow()
|
101
94
|
start_time = end_time - timedelta(minutes=duration_minutes)
|
102
|
-
|
95
|
+
|
103
96
|
response = cloudwatch_client.get_metric_data(
|
104
97
|
MetricDataQueries=[
|
105
98
|
{
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
{
|
113
|
-
'Name': 'DBInstanceIdentifier',
|
114
|
-
'Value': db_identifier
|
115
|
-
}
|
116
|
-
]
|
99
|
+
"Id": "cpu",
|
100
|
+
"MetricStat": {
|
101
|
+
"Metric": {
|
102
|
+
"Namespace": "AWS/RDS",
|
103
|
+
"MetricName": "CPUUtilization",
|
104
|
+
"Dimensions": [{"Name": "DBInstanceIdentifier", "Value": db_identifier}],
|
117
105
|
},
|
118
|
-
|
119
|
-
|
106
|
+
"Period": 3600, # 1 hour periods
|
107
|
+
"Stat": "Average",
|
120
108
|
},
|
121
|
-
|
109
|
+
"ReturnData": True,
|
122
110
|
}
|
123
111
|
],
|
124
112
|
StartTime=start_time,
|
125
|
-
EndTime=end_time
|
113
|
+
EndTime=end_time,
|
126
114
|
)
|
127
|
-
|
115
|
+
|
128
116
|
# Process CPU utilization data
|
129
|
-
if
|
130
|
-
response[
|
131
|
-
|
132
|
-
cpu_values = response['MetricDataResults'][0]['Values']
|
117
|
+
if response["MetricDataResults"] and response["MetricDataResults"][0]["Values"]:
|
118
|
+
cpu_values = response["MetricDataResults"][0]["Values"]
|
133
119
|
avg_cpu = sum(cpu_values) / len(cpu_values)
|
134
|
-
|
120
|
+
|
135
121
|
if avg_cpu < utilization_threshold:
|
136
122
|
instance_info = {
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
123
|
+
"region": region,
|
124
|
+
"instance_id": db_identifier,
|
125
|
+
"instance_class": db.get("DBInstanceClass"),
|
126
|
+
"engine": db.get("Engine"),
|
127
|
+
"avg_cpu_utilization": round(avg_cpu, 2),
|
128
|
+
"status": db.get("DBInstanceStatus"),
|
129
|
+
"creation_time": db.get("InstanceCreateTime"),
|
144
130
|
}
|
145
131
|
region_low_cpu_instances.append(instance_info)
|
146
|
-
|
132
|
+
|
147
133
|
except ClientError as e:
|
148
134
|
print_warning(f"Could not get metrics for {db_identifier}: {e}")
|
149
135
|
continue
|
150
|
-
|
136
|
+
|
151
137
|
if region_low_cpu_instances:
|
152
|
-
results.append(
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
138
|
+
results.append(
|
139
|
+
OperationResult(
|
140
|
+
status=OperationStatus.SUCCESS,
|
141
|
+
data=region_low_cpu_instances,
|
142
|
+
metadata={
|
143
|
+
"region": region,
|
144
|
+
"threshold": utilization_threshold,
|
145
|
+
"duration_minutes": duration_minutes,
|
146
|
+
"instances_found": len(region_low_cpu_instances),
|
147
|
+
},
|
148
|
+
)
|
149
|
+
)
|
150
|
+
|
163
151
|
print_info(f"Found {len(region_low_cpu_instances)} low CPU instances in {region}")
|
164
|
-
|
152
|
+
|
165
153
|
except ClientError as e:
|
166
154
|
print_error(f"Error analyzing region {region}: {e}")
|
167
|
-
results.append(
|
168
|
-
status=OperationStatus.FAILED,
|
169
|
-
|
170
|
-
|
171
|
-
))
|
172
|
-
|
155
|
+
results.append(
|
156
|
+
OperationResult(status=OperationStatus.FAILED, error=str(e), metadata={"region": region})
|
157
|
+
)
|
158
|
+
|
173
159
|
if results:
|
174
|
-
total_instances = sum(
|
175
|
-
len(r.data) for r in results
|
176
|
-
if r.status == OperationStatus.SUCCESS and r.data
|
177
|
-
)
|
160
|
+
total_instances = sum(len(r.data) for r in results if r.status == OperationStatus.SUCCESS and r.data)
|
178
161
|
print_success(f"Analysis complete. Found {total_instances} instances below {utilization_threshold}% CPU")
|
179
162
|
else:
|
180
163
|
print_info("No instances found below threshold")
|
181
|
-
|
164
|
+
|
182
165
|
return results
|
183
166
|
|
184
167
|
def get_publicly_accessible_instances(self, context: OperationContext) -> List[OperationResult]:
|
185
168
|
"""
|
186
169
|
Get all publicly accessible RDS instances.
|
187
|
-
|
170
|
+
|
188
171
|
Extracted from: AWS_Secure_Publicly_Accessible_RDS_Instances.ipynb
|
189
|
-
|
172
|
+
|
190
173
|
Args:
|
191
174
|
context: Operation context
|
192
|
-
|
175
|
+
|
193
176
|
Returns:
|
194
177
|
List of operation results with publicly accessible instances
|
195
178
|
"""
|
196
179
|
print_header("RDS Public Access Analysis", "Security Compliance")
|
197
|
-
|
180
|
+
|
198
181
|
results = []
|
199
182
|
regions_to_check = [context.region] if context.region else self._get_all_regions()
|
200
|
-
|
183
|
+
|
201
184
|
for region in track(regions_to_check, description="Scanning regions..."):
|
202
185
|
try:
|
203
186
|
session = self._get_aws_session(context.profile)
|
204
|
-
rds_client = session.client(
|
205
|
-
|
187
|
+
rds_client = session.client("rds", region_name=region)
|
188
|
+
|
206
189
|
# Get all RDS instances
|
207
|
-
instances = get_paginated_results(
|
208
|
-
|
209
|
-
'describe_db_instances',
|
210
|
-
'DBInstances'
|
211
|
-
)
|
212
|
-
|
190
|
+
instances = get_paginated_results(rds_client, "describe_db_instances", "DBInstances")
|
191
|
+
|
213
192
|
public_instances = []
|
214
|
-
|
193
|
+
|
215
194
|
for db in instances:
|
216
|
-
if db.get(
|
195
|
+
if db.get("PubliclyAccessible", False):
|
217
196
|
instance_info = {
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
197
|
+
"region": region,
|
198
|
+
"instance_id": db["DBInstanceIdentifier"],
|
199
|
+
"instance_class": db.get("DBInstanceClass"),
|
200
|
+
"engine": db.get("Engine"),
|
201
|
+
"status": db.get("DBInstanceStatus"),
|
202
|
+
"endpoint": db.get("Endpoint", {}).get("Address"),
|
203
|
+
"port": db.get("Endpoint", {}).get("Port"),
|
204
|
+
"vpc_id": db.get("DBSubnetGroup", {}).get("VpcId") if db.get("DBSubnetGroup") else None,
|
226
205
|
}
|
227
206
|
public_instances.append(instance_info)
|
228
|
-
|
207
|
+
|
229
208
|
if public_instances:
|
230
|
-
results.append(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
))
|
209
|
+
results.append(
|
210
|
+
OperationResult(
|
211
|
+
status=OperationStatus.SUCCESS,
|
212
|
+
data=public_instances,
|
213
|
+
metadata={"region": region, "public_instances_found": len(public_instances)},
|
214
|
+
)
|
215
|
+
)
|
238
216
|
print_warning(f"Found {len(public_instances)} publicly accessible instances in {region}")
|
239
|
-
|
217
|
+
|
240
218
|
except ClientError as e:
|
241
219
|
print_error(f"Error scanning region {region}: {e}")
|
242
|
-
results.append(
|
243
|
-
status=OperationStatus.FAILED,
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
total_public = sum(
|
249
|
-
len(r.data) for r in results
|
250
|
-
if r.status == OperationStatus.SUCCESS and r.data
|
251
|
-
)
|
252
|
-
|
220
|
+
results.append(
|
221
|
+
OperationResult(status=OperationStatus.FAILED, error=str(e), metadata={"region": region})
|
222
|
+
)
|
223
|
+
|
224
|
+
total_public = sum(len(r.data) for r in results if r.status == OperationStatus.SUCCESS and r.data)
|
225
|
+
|
253
226
|
if total_public > 0:
|
254
227
|
print_warning(f"Security Alert: {total_public} publicly accessible RDS instances found")
|
255
228
|
else:
|
256
229
|
print_success("No publicly accessible RDS instances found")
|
257
|
-
|
230
|
+
|
258
231
|
return results
|
259
232
|
|
260
233
|
def secure_public_instances(
|
261
|
-
self,
|
262
|
-
context: OperationContext,
|
263
|
-
instance_identifiers: List[str]
|
234
|
+
self, context: OperationContext, instance_identifiers: List[str]
|
264
235
|
) -> List[OperationResult]:
|
265
236
|
"""
|
266
237
|
Make RDS instances not publicly accessible.
|
267
|
-
|
238
|
+
|
268
239
|
Extracted from: AWS_Secure_Publicly_Accessible_RDS_Instances.ipynb
|
269
|
-
|
240
|
+
|
270
241
|
Args:
|
271
242
|
context: Operation context
|
272
243
|
instance_identifiers: List of instance identifiers to secure
|
273
|
-
|
244
|
+
|
274
245
|
Returns:
|
275
246
|
List of operation results
|
276
247
|
"""
|
277
248
|
print_header("RDS Security Remediation", "Making Instances Private")
|
278
|
-
|
249
|
+
|
279
250
|
if self.dry_run:
|
280
251
|
print_info("DRY RUN: Would secure the following instances:")
|
281
252
|
for instance_id in instance_identifiers:
|
282
253
|
print_info(f" - {instance_id} (would set PubliclyAccessible=False)")
|
283
|
-
return [
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
254
|
+
return [
|
255
|
+
OperationResult(
|
256
|
+
status=OperationStatus.SUCCESS,
|
257
|
+
data={"dry_run": True, "instances": instance_identifiers},
|
258
|
+
metadata={"operation": "secure_instances_dry_run"},
|
259
|
+
)
|
260
|
+
]
|
261
|
+
|
289
262
|
results = []
|
290
|
-
|
263
|
+
|
291
264
|
for instance_identifier in track(instance_identifiers, description="Securing instances..."):
|
292
265
|
try:
|
293
266
|
# Determine region for instance (simplified - assumes current region)
|
294
267
|
session = self._get_aws_session(context.profile)
|
295
|
-
rds_client = session.client(
|
296
|
-
|
268
|
+
rds_client = session.client("rds", region_name=context.region)
|
269
|
+
|
297
270
|
# Modify instance to make it not publicly accessible
|
298
271
|
response = rds_client.modify_db_instance(
|
299
|
-
DBInstanceIdentifier=instance_identifier,
|
300
|
-
PubliclyAccessible=False
|
272
|
+
DBInstanceIdentifier=instance_identifier, PubliclyAccessible=False
|
301
273
|
)
|
302
|
-
|
303
|
-
results.append(
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
274
|
+
|
275
|
+
results.append(
|
276
|
+
OperationResult(
|
277
|
+
status=OperationStatus.SUCCESS,
|
278
|
+
data={"instance_id": instance_identifier, "response": response},
|
279
|
+
metadata={"operation": "modify_public_access"},
|
280
|
+
)
|
281
|
+
)
|
282
|
+
|
309
283
|
print_success(f"Initiated security modification for {instance_identifier}")
|
310
|
-
|
284
|
+
|
311
285
|
except ClientError as e:
|
312
286
|
print_error(f"Failed to secure instance {instance_identifier}: {e}")
|
313
|
-
results.append(
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
)
|
318
|
-
|
287
|
+
results.append(
|
288
|
+
OperationResult(
|
289
|
+
status=OperationStatus.FAILED, error=str(e), metadata={"instance_id": instance_identifier}
|
290
|
+
)
|
291
|
+
)
|
292
|
+
|
319
293
|
return results
|
320
294
|
|
321
295
|
def analyze_reserved_instance_opportunities(
|
322
|
-
self,
|
323
|
-
context: OperationContext,
|
324
|
-
threshold_days: int = 30
|
296
|
+
self, context: OperationContext, threshold_days: int = 30
|
325
297
|
) -> List[OperationResult]:
|
326
298
|
"""
|
327
299
|
Find long-running instances without reserved instances.
|
328
|
-
|
300
|
+
|
329
301
|
Extracted from: AWS_Purchase_Reserved_Instances_For_Long_Running_RDS_Instances.ipynb
|
330
|
-
|
302
|
+
|
331
303
|
Args:
|
332
304
|
context: Operation context
|
333
305
|
threshold_days: Minimum days running to consider for RI (default 30)
|
334
|
-
|
306
|
+
|
335
307
|
Returns:
|
336
308
|
List of operation results with RI opportunities
|
337
309
|
"""
|
338
310
|
print_header("RDS Reserved Instance Analysis", "Cost Optimization")
|
339
|
-
|
311
|
+
|
340
312
|
results = []
|
341
313
|
regions_to_check = [context.region] if context.region else self._get_all_regions()
|
342
|
-
|
314
|
+
|
343
315
|
for region in track(regions_to_check, description="Analyzing RI opportunities..."):
|
344
316
|
try:
|
345
317
|
session = self._get_aws_session(context.profile)
|
346
|
-
rds_client = session.client(
|
347
|
-
|
318
|
+
rds_client = session.client("rds", region_name=region)
|
319
|
+
|
348
320
|
# Get reserved instances per region
|
349
321
|
reserved_response = rds_client.describe_reserved_db_instances()
|
350
322
|
reserved_by_class = {}
|
351
|
-
|
352
|
-
for reserved in reserved_response.get(
|
353
|
-
instance_class = reserved[
|
354
|
-
reserved_by_class[instance_class] = reserved_by_class.get(instance_class, 0) + reserved.get(
|
355
|
-
|
323
|
+
|
324
|
+
for reserved in reserved_response.get("ReservedDBInstances", []):
|
325
|
+
instance_class = reserved["DBInstanceClass"]
|
326
|
+
reserved_by_class[instance_class] = reserved_by_class.get(instance_class, 0) + reserved.get(
|
327
|
+
"DBInstanceCount", 1
|
328
|
+
)
|
329
|
+
|
356
330
|
# Get running instances
|
357
|
-
instances = get_paginated_results(
|
358
|
-
|
359
|
-
'describe_db_instances',
|
360
|
-
'DBInstances'
|
361
|
-
)
|
362
|
-
|
331
|
+
instances = get_paginated_results(rds_client, "describe_db_instances", "DBInstances")
|
332
|
+
|
363
333
|
ri_opportunities = []
|
364
|
-
|
334
|
+
|
365
335
|
for db in instances:
|
366
|
-
if db.get(
|
336
|
+
if db.get("DBInstanceStatus") == "available":
|
367
337
|
# Check if instance has been running long enough
|
368
|
-
create_time = db.get(
|
338
|
+
create_time = db.get("InstanceCreateTime")
|
369
339
|
if create_time:
|
370
340
|
uptime = datetime.now(timezone.utc) - create_time
|
371
341
|
if uptime > timedelta(days=threshold_days):
|
372
|
-
|
373
|
-
instance_class = db.get('DBInstanceClass')
|
342
|
+
instance_class = db.get("DBInstanceClass")
|
374
343
|
reserved_count = reserved_by_class.get(instance_class, 0)
|
375
|
-
|
344
|
+
|
376
345
|
# Count running instances of this class
|
377
346
|
running_count = sum(
|
378
|
-
1
|
379
|
-
|
380
|
-
|
347
|
+
1
|
348
|
+
for inst in instances
|
349
|
+
if (
|
350
|
+
inst.get("DBInstanceClass") == instance_class
|
351
|
+
and inst.get("DBInstanceStatus") == "available"
|
352
|
+
)
|
381
353
|
)
|
382
|
-
|
354
|
+
|
383
355
|
if running_count > reserved_count:
|
384
356
|
ri_opportunity = {
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
357
|
+
"region": region,
|
358
|
+
"instance_id": db["DBInstanceIdentifier"],
|
359
|
+
"instance_class": instance_class,
|
360
|
+
"engine": db.get("Engine"),
|
361
|
+
"running_days": uptime.days,
|
362
|
+
"reserved_count": reserved_count,
|
363
|
+
"running_count": running_count,
|
364
|
+
"ri_gap": running_count - reserved_count,
|
393
365
|
}
|
394
366
|
ri_opportunities.append(ri_opportunity)
|
395
|
-
|
367
|
+
|
396
368
|
if ri_opportunities:
|
397
|
-
results.append(
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
369
|
+
results.append(
|
370
|
+
OperationResult(
|
371
|
+
status=OperationStatus.SUCCESS,
|
372
|
+
data=ri_opportunities,
|
373
|
+
metadata={
|
374
|
+
"region": region,
|
375
|
+
"threshold_days": threshold_days,
|
376
|
+
"opportunities_found": len(ri_opportunities),
|
377
|
+
},
|
378
|
+
)
|
379
|
+
)
|
380
|
+
|
407
381
|
print_info(f"Found {len(ri_opportunities)} RI opportunities in {region}")
|
408
|
-
|
382
|
+
|
409
383
|
except ClientError as e:
|
410
384
|
print_error(f"Error analyzing RI opportunities in {region}: {e}")
|
411
|
-
results.append(
|
412
|
-
status=OperationStatus.FAILED,
|
413
|
-
|
414
|
-
|
415
|
-
))
|
416
|
-
|
385
|
+
results.append(
|
386
|
+
OperationResult(status=OperationStatus.FAILED, error=str(e), metadata={"region": region})
|
387
|
+
)
|
388
|
+
|
417
389
|
return results
|
418
390
|
|
419
391
|
def delete_instance_safely(
|
420
|
-
self,
|
421
|
-
context: OperationContext,
|
422
|
-
instance_identifier: str,
|
423
|
-
skip_final_snapshot: bool = False
|
392
|
+
self, context: OperationContext, instance_identifier: str, skip_final_snapshot: bool = False
|
424
393
|
) -> OperationResult:
|
425
394
|
"""
|
426
395
|
Safely delete an RDS instance with proper safeguards.
|
427
|
-
|
396
|
+
|
428
397
|
Extracted from: AWS_Delete_RDS_Instances_with_Low_CPU_Utilization.ipynb
|
429
|
-
|
398
|
+
|
430
399
|
Args:
|
431
400
|
context: Operation context
|
432
401
|
instance_identifier: RDS instance identifier
|
433
402
|
skip_final_snapshot: Whether to skip final snapshot (default False)
|
434
|
-
|
403
|
+
|
435
404
|
Returns:
|
436
405
|
Operation result
|
437
406
|
"""
|
438
407
|
print_header(f"RDS Instance Deletion", f"Instance: {instance_identifier}")
|
439
|
-
|
408
|
+
|
440
409
|
if self.dry_run:
|
441
410
|
print_warning(f"DRY RUN: Would delete RDS instance {instance_identifier}")
|
442
411
|
return OperationResult(
|
443
412
|
status=OperationStatus.SUCCESS,
|
444
|
-
data={
|
445
|
-
metadata={
|
413
|
+
data={"dry_run": True, "instance_id": instance_identifier},
|
414
|
+
metadata={"operation": "delete_instance_dry_run"},
|
446
415
|
)
|
447
|
-
|
416
|
+
|
448
417
|
# Safety confirmation for destructive operation
|
449
418
|
print_warning(f"DESTRUCTIVE OPERATION: Preparing to delete RDS instance {instance_identifier}")
|
450
|
-
|
419
|
+
|
451
420
|
try:
|
452
421
|
session = self._get_aws_session(context.profile)
|
453
|
-
rds_client = session.client(
|
454
|
-
|
455
|
-
delete_params = {
|
456
|
-
|
457
|
-
'SkipFinalSnapshot': skip_final_snapshot
|
458
|
-
}
|
459
|
-
|
422
|
+
rds_client = session.client("rds", region_name=context.region)
|
423
|
+
|
424
|
+
delete_params = {"DBInstanceIdentifier": instance_identifier, "SkipFinalSnapshot": skip_final_snapshot}
|
425
|
+
|
460
426
|
if not skip_final_snapshot:
|
461
427
|
# Create snapshot with timestamp
|
462
|
-
timestamp = datetime.utcnow().strftime(
|
463
|
-
delete_params[
|
428
|
+
timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H-%M")
|
429
|
+
delete_params["FinalDBSnapshotIdentifier"] = f"{instance_identifier}-final-{timestamp}"
|
464
430
|
print_info(f"Creating final snapshot: {delete_params['FinalDBSnapshotIdentifier']}")
|
465
|
-
|
431
|
+
|
466
432
|
response = rds_client.delete_db_instance(**delete_params)
|
467
|
-
|
433
|
+
|
468
434
|
print_success(f"RDS instance {instance_identifier} deletion initiated")
|
469
|
-
|
435
|
+
|
470
436
|
return OperationResult(
|
471
437
|
status=OperationStatus.SUCCESS,
|
472
|
-
data={
|
473
|
-
metadata={
|
438
|
+
data={"instance_id": instance_identifier, "response": response},
|
439
|
+
metadata={"operation": "delete_instance", "final_snapshot": not skip_final_snapshot},
|
474
440
|
)
|
475
|
-
|
441
|
+
|
476
442
|
except ClientError as e:
|
477
443
|
print_error(f"Failed to delete instance {instance_identifier}: {e}")
|
478
444
|
return OperationResult(
|
479
|
-
status=OperationStatus.FAILED,
|
480
|
-
error=str(e),
|
481
|
-
metadata={'instance_id': instance_identifier}
|
445
|
+
status=OperationStatus.FAILED, error=str(e), metadata={"instance_id": instance_identifier}
|
482
446
|
)
|
483
447
|
|
484
448
|
def execute_operation(self, operation: str, context: OperationContext, **kwargs) -> List[OperationResult]:
|
485
449
|
"""Execute the specified RDS operation."""
|
486
450
|
if operation not in self.supported_operations:
|
487
451
|
raise ValueError(f"Unsupported operation: {operation}")
|
488
|
-
|
452
|
+
|
489
453
|
operation_method = getattr(self, operation)
|
490
454
|
return operation_method(context, **kwargs)
|
491
455
|
|
@@ -493,16 +457,21 @@ class RDSOperations(BaseOperation):
|
|
493
457
|
"""Get all available AWS regions for RDS."""
|
494
458
|
try:
|
495
459
|
session = boto3.Session()
|
496
|
-
return session.get_available_regions(
|
460
|
+
return session.get_available_regions("rds")
|
497
461
|
except Exception:
|
498
462
|
# Fallback to common regions if API call fails
|
499
463
|
return [
|
500
|
-
|
501
|
-
|
464
|
+
"us-east-1",
|
465
|
+
"us-west-2",
|
466
|
+
"eu-west-1",
|
467
|
+
"ap-southeast-1",
|
468
|
+
"us-west-1",
|
469
|
+
"eu-central-1",
|
470
|
+
"ap-southeast-2",
|
502
471
|
]
|
503
472
|
|
504
473
|
def _get_aws_session(self, profile: Optional[str] = None) -> boto3.Session:
|
505
474
|
"""Get AWS session with proper profile handling."""
|
506
475
|
if profile:
|
507
476
|
return boto3.Session(profile_name=profile)
|
508
|
-
return boto3.Session()
|
477
|
+
return boto3.Session()
|