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.
Files changed (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {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 = 'rds'
43
+ self.service = "rds"
44
44
  self.supported_operations = {
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'
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('rds', region_name=region)
84
- cloudwatch_client = session.client('cloudwatch', region_name=region)
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
- rds_client,
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['DBInstanceIdentifier']
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
- 'Id': 'cpu',
107
- 'MetricStat': {
108
- 'Metric': {
109
- 'Namespace': 'AWS/RDS',
110
- 'MetricName': 'CPUUtilization',
111
- 'Dimensions': [
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
- 'Period': 3600, # 1 hour periods
119
- 'Stat': 'Average'
106
+ "Period": 3600, # 1 hour periods
107
+ "Stat": "Average",
120
108
  },
121
- 'ReturnData': True
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 (response['MetricDataResults'] and
130
- response['MetricDataResults'][0]['Values']):
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
- 'region': region,
138
- 'instance_id': db_identifier,
139
- 'instance_class': db.get('DBInstanceClass'),
140
- 'engine': db.get('Engine'),
141
- 'avg_cpu_utilization': round(avg_cpu, 2),
142
- 'status': db.get('DBInstanceStatus'),
143
- 'creation_time': db.get('InstanceCreateTime')
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(OperationResult(
153
- status=OperationStatus.SUCCESS,
154
- data=region_low_cpu_instances,
155
- metadata={
156
- 'region': region,
157
- 'threshold': utilization_threshold,
158
- 'duration_minutes': duration_minutes,
159
- 'instances_found': len(region_low_cpu_instances)
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(OperationResult(
168
- status=OperationStatus.FAILED,
169
- error=str(e),
170
- metadata={'region': region}
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('rds', region_name=region)
205
-
187
+ rds_client = session.client("rds", region_name=region)
188
+
206
189
  # Get all RDS instances
207
- instances = get_paginated_results(
208
- rds_client,
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('PubliclyAccessible', False):
195
+ if db.get("PubliclyAccessible", False):
217
196
  instance_info = {
218
- 'region': region,
219
- 'instance_id': db['DBInstanceIdentifier'],
220
- 'instance_class': db.get('DBInstanceClass'),
221
- 'engine': db.get('Engine'),
222
- 'status': db.get('DBInstanceStatus'),
223
- 'endpoint': db.get('Endpoint', {}).get('Address'),
224
- 'port': db.get('Endpoint', {}).get('Port'),
225
- 'vpc_id': db.get('DBSubnetGroup', {}).get('VpcId') if db.get('DBSubnetGroup') else None
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(OperationResult(
231
- status=OperationStatus.SUCCESS,
232
- data=public_instances,
233
- metadata={
234
- 'region': region,
235
- 'public_instances_found': len(public_instances)
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(OperationResult(
243
- status=OperationStatus.FAILED,
244
- error=str(e),
245
- metadata={'region': region}
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 [OperationResult(
284
- status=OperationStatus.SUCCESS,
285
- data={'dry_run': True, 'instances': instance_identifiers},
286
- metadata={'operation': 'secure_instances_dry_run'}
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('rds', region_name=context.region)
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(OperationResult(
304
- status=OperationStatus.SUCCESS,
305
- data={'instance_id': instance_identifier, 'response': response},
306
- metadata={'operation': 'modify_public_access'}
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(OperationResult(
314
- status=OperationStatus.FAILED,
315
- error=str(e),
316
- metadata={'instance_id': instance_identifier}
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('rds', region_name=region)
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('ReservedDBInstances', []):
353
- instance_class = reserved['DBInstanceClass']
354
- reserved_by_class[instance_class] = reserved_by_class.get(instance_class, 0) + reserved.get('DBInstanceCount', 1)
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
- rds_client,
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('DBInstanceStatus') == 'available':
336
+ if db.get("DBInstanceStatus") == "available":
367
337
  # Check if instance has been running long enough
368
- create_time = db.get('InstanceCreateTime')
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 for inst in instances
379
- if (inst.get('DBInstanceClass') == instance_class and
380
- inst.get('DBInstanceStatus') == 'available')
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
- 'region': region,
386
- 'instance_id': db['DBInstanceIdentifier'],
387
- 'instance_class': instance_class,
388
- 'engine': db.get('Engine'),
389
- 'running_days': uptime.days,
390
- 'reserved_count': reserved_count,
391
- 'running_count': running_count,
392
- 'ri_gap': running_count - reserved_count
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(OperationResult(
398
- status=OperationStatus.SUCCESS,
399
- data=ri_opportunities,
400
- metadata={
401
- 'region': region,
402
- 'threshold_days': threshold_days,
403
- 'opportunities_found': len(ri_opportunities)
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(OperationResult(
412
- status=OperationStatus.FAILED,
413
- error=str(e),
414
- metadata={'region': region}
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={'dry_run': True, 'instance_id': instance_identifier},
445
- metadata={'operation': 'delete_instance_dry_run'}
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('rds', region_name=context.region)
454
-
455
- delete_params = {
456
- 'DBInstanceIdentifier': instance_identifier,
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('%Y-%m-%d-%H-%M')
463
- delete_params['FinalDBSnapshotIdentifier'] = f"{instance_identifier}-final-{timestamp}"
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={'instance_id': instance_identifier, 'response': response},
473
- metadata={'operation': 'delete_instance', 'final_snapshot': not skip_final_snapshot}
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('rds')
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
- 'us-east-1', 'us-west-2', 'eu-west-1', 'ap-southeast-1',
501
- 'us-west-1', 'eu-central-1', 'ap-southeast-2'
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()