runbooks 0.7.0__py3-none-any.whl → 0.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. runbooks/__init__.py +87 -37
  2. runbooks/cfat/README.md +300 -49
  3. runbooks/cfat/__init__.py +2 -2
  4. runbooks/finops/__init__.py +1 -1
  5. runbooks/finops/cli.py +1 -1
  6. runbooks/inventory/collectors/__init__.py +8 -0
  7. runbooks/inventory/collectors/aws_management.py +791 -0
  8. runbooks/inventory/collectors/aws_networking.py +3 -3
  9. runbooks/main.py +3389 -782
  10. runbooks/operate/__init__.py +207 -0
  11. runbooks/operate/base.py +311 -0
  12. runbooks/operate/cloudformation_operations.py +619 -0
  13. runbooks/operate/cloudwatch_operations.py +496 -0
  14. runbooks/operate/dynamodb_operations.py +812 -0
  15. runbooks/operate/ec2_operations.py +926 -0
  16. runbooks/operate/iam_operations.py +569 -0
  17. runbooks/operate/s3_operations.py +1211 -0
  18. runbooks/operate/tagging_operations.py +655 -0
  19. runbooks/remediation/CLAUDE.md +100 -0
  20. runbooks/remediation/DOME9.md +218 -0
  21. runbooks/remediation/README.md +26 -0
  22. runbooks/remediation/Tests/__init__.py +0 -0
  23. runbooks/remediation/Tests/update_policy.py +74 -0
  24. runbooks/remediation/__init__.py +95 -0
  25. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  26. runbooks/remediation/acm_remediation.py +875 -0
  27. runbooks/remediation/api_gateway_list.py +167 -0
  28. runbooks/remediation/base.py +643 -0
  29. runbooks/remediation/cloudtrail_remediation.py +908 -0
  30. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  31. runbooks/remediation/cognito_active_users.py +78 -0
  32. runbooks/remediation/cognito_remediation.py +856 -0
  33. runbooks/remediation/cognito_user_password_reset.py +163 -0
  34. runbooks/remediation/commons.py +455 -0
  35. runbooks/remediation/dynamodb_optimize.py +155 -0
  36. runbooks/remediation/dynamodb_remediation.py +744 -0
  37. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  38. runbooks/remediation/ec2_public_ips.py +134 -0
  39. runbooks/remediation/ec2_remediation.py +892 -0
  40. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  41. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  42. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  43. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  44. runbooks/remediation/kms_remediation.py +717 -0
  45. runbooks/remediation/lambda_list.py +243 -0
  46. runbooks/remediation/lambda_remediation.py +971 -0
  47. runbooks/remediation/multi_account.py +569 -0
  48. runbooks/remediation/rds_instance_list.py +199 -0
  49. runbooks/remediation/rds_remediation.py +873 -0
  50. runbooks/remediation/rds_snapshot_list.py +192 -0
  51. runbooks/remediation/requirements.txt +118 -0
  52. runbooks/remediation/s3_block_public_access.py +159 -0
  53. runbooks/remediation/s3_bucket_public_access.py +143 -0
  54. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  55. runbooks/remediation/s3_downloader.py +215 -0
  56. runbooks/remediation/s3_enable_access_logging.py +562 -0
  57. runbooks/remediation/s3_encryption.py +526 -0
  58. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  59. runbooks/remediation/s3_list.py +141 -0
  60. runbooks/remediation/s3_object_search.py +201 -0
  61. runbooks/remediation/s3_remediation.py +816 -0
  62. runbooks/remediation/scan_for_phrase.py +425 -0
  63. runbooks/remediation/workspaces_list.py +220 -0
  64. runbooks/security/__init__.py +9 -10
  65. runbooks/security/security_baseline_tester.py +4 -2
  66. runbooks-0.7.5.dist-info/METADATA +606 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/RECORD +72 -44
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/entry_points.txt +0 -1
  69. runbooks/aws/__init__.py +0 -58
  70. runbooks/aws/dynamodb_operations.py +0 -231
  71. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  72. runbooks/aws/ec2_describe_instances.py +0 -202
  73. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  74. runbooks/aws/ec2_run_instances.py +0 -213
  75. runbooks/aws/ec2_start_stop_instances.py +0 -212
  76. runbooks/aws/ec2_terminate_instances.py +0 -143
  77. runbooks/aws/ec2_unused_eips.py +0 -196
  78. runbooks/aws/ec2_unused_volumes.py +0 -188
  79. runbooks/aws/s3_create_bucket.py +0 -142
  80. runbooks/aws/s3_list_buckets.py +0 -152
  81. runbooks/aws/s3_list_objects.py +0 -156
  82. runbooks/aws/s3_object_operations.py +0 -183
  83. runbooks/aws/tagging_lambda_handler.py +0 -183
  84. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  85. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  86. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  87. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  88. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  89. runbooks/inventory/update_aws_actions.py +0 -173
  90. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  91. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  92. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  93. runbooks/inventory/update_s3_public_access_block.py +0 -539
  94. runbooks/organizations/__init__.py +0 -12
  95. runbooks/organizations/manager.py +0 -374
  96. runbooks-0.7.0.dist-info/METADATA +0 -375
  97. /runbooks/{aws → operate}/tags.json +0 -0
  98. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/WHEEL +0 -0
  99. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/licenses/LICENSE +0 -0
  100. {runbooks-0.7.0.dist-info → runbooks-0.7.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,199 @@
1
+ """
2
+ RDS Instance Cost Analysis - Analyze RDS instances for cost optimization opportunities.
3
+ """
4
+
5
+ import logging
6
+ from datetime import datetime, timedelta, timezone
7
+ from functools import lru_cache
8
+
9
+ import click
10
+ from botocore.exceptions import ClientError
11
+
12
+ from .commons import display_aws_account_info, get_client, write_to_csv
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @lru_cache(maxsize=32)
18
+ def get_rds_reserved_instances():
19
+ """Get RDS reserved instances with caching for performance."""
20
+ try:
21
+ rds_client = get_client("rds")
22
+ response = rds_client.describe_reserved_db_instances()
23
+ logger.debug(f"Found {len(response.get('ReservedDBInstances', []))} reserved instances")
24
+ return response
25
+ except ClientError as e:
26
+ logger.warning(f"Could not get reserved instances: {e}")
27
+ return {"ReservedDBInstances": []}
28
+
29
+
30
+ def get_cloudwatch_metrics(cloudwatch, instance_id, metric_name, days=7):
31
+ """Get CloudWatch metrics for RDS instance."""
32
+ try:
33
+ end_time = datetime.now(tz=timezone.utc)
34
+ start_time = end_time - timedelta(days=days)
35
+
36
+ response = cloudwatch.get_metric_statistics(
37
+ Namespace="AWS/RDS",
38
+ MetricName=metric_name,
39
+ Dimensions=[{"Name": "DBInstanceIdentifier", "Value": instance_id}],
40
+ StartTime=start_time,
41
+ EndTime=end_time,
42
+ Period=86400, # Daily average
43
+ Statistics=["Average", "Maximum"],
44
+ )
45
+
46
+ datapoints = response.get("Datapoints", [])
47
+ if datapoints:
48
+ avg_value = sum(dp["Average"] for dp in datapoints) / len(datapoints)
49
+ max_value = max(dp["Maximum"] for dp in datapoints)
50
+ return round(avg_value, 2), round(max_value, 2)
51
+ else:
52
+ logger.debug(f"No metrics data for {instance_id} - {metric_name}")
53
+ return 0, 0
54
+
55
+ except ClientError as e:
56
+ logger.warning(f"Could not get {metric_name} metrics for {instance_id}: {e}")
57
+ return 0, 0
58
+
59
+
60
+ @click.command()
61
+ @click.option("--output-file", default="/tmp/rds_cost_optimization.csv", help="Output CSV file path")
62
+ @click.option("--days", default=7, help="Number of days for CloudWatch metrics analysis")
63
+ @click.option("--include-storage", is_flag=True, help="Include detailed storage analysis")
64
+ def get_rds_list(output_file, days, include_storage):
65
+ """Analyze RDS instances for cost optimization and performance insights."""
66
+ logger.info(f"Analyzing RDS instances in {display_aws_account_info()}")
67
+
68
+ try:
69
+ rds = get_client("rds")
70
+ cloudwatch = get_client("cloudwatch")
71
+
72
+ # Get RDS instances
73
+ logger.info("Collecting RDS instance data...")
74
+ response = rds.describe_db_instances()
75
+ instances = response.get("DBInstances", [])
76
+
77
+ if not instances:
78
+ logger.info("No RDS instances found")
79
+ return
80
+
81
+ logger.info(f"Found {len(instances)} RDS instances to analyze")
82
+
83
+ # Get reserved instances for cost analysis
84
+ reserved_instances = get_rds_reserved_instances()
85
+ reserved_classes = {
86
+ ri["DBInstanceClass"]
87
+ for ri in reserved_instances.get("ReservedDBInstances", [])
88
+ if ri.get("State") == "active"
89
+ }
90
+
91
+ data = []
92
+ optimization_candidates = []
93
+
94
+ for i, instance in enumerate(instances, 1):
95
+ instance_id = instance["DBInstanceIdentifier"]
96
+ instance_class = instance["DBInstanceClass"]
97
+
98
+ logger.info(f"Analyzing instance {i}/{len(instances)}: {instance_id}")
99
+
100
+ # Basic instance information
101
+ instance_data = {
102
+ "InstanceIdentifier": instance_id,
103
+ "Engine": instance.get("Engine", "Unknown"),
104
+ "EngineVersion": instance.get("EngineVersion", "Unknown"),
105
+ "InstanceClass": instance_class,
106
+ "StorageType": instance.get("StorageType", "Unknown"),
107
+ "AllocatedStorage": instance.get("AllocatedStorage", 0),
108
+ "MultiAZ": instance.get("MultiAZ", False),
109
+ "DBInstanceStatus": instance.get("DBInstanceStatus", "Unknown"),
110
+ "AvailabilityZone": instance.get("AvailabilityZone", "Unknown"),
111
+ }
112
+
113
+ # Determine purchase type
114
+ purchase_type = "Reserved" if instance_class in reserved_classes else "On-Demand"
115
+ instance_data["PurchaseType"] = purchase_type
116
+
117
+ # Get CloudWatch metrics
118
+ logger.debug(f" Getting CloudWatch metrics for {instance_id}...")
119
+
120
+ # CPU Utilization
121
+ cpu_avg, cpu_max = get_cloudwatch_metrics(cloudwatch, instance_id, "CPUUtilization", days)
122
+ instance_data[f"CPUUtilization_Avg_{days}d"] = cpu_avg
123
+ instance_data[f"CPUUtilization_Max_{days}d"] = cpu_max
124
+
125
+ # Memory metrics (FreeableMemory)
126
+ mem_avg, mem_max = get_cloudwatch_metrics(cloudwatch, instance_id, "FreeableMemory", days)
127
+ # Convert bytes to GB
128
+ mem_avg_gb = round(mem_avg / (1024**3), 2) if mem_avg > 0 else 0
129
+ mem_max_gb = round(mem_max / (1024**3), 2) if mem_max > 0 else 0
130
+ instance_data[f"FreeMemory_Avg_GB_{days}d"] = mem_avg_gb
131
+ instance_data[f"FreeMemory_Max_GB_{days}d"] = mem_max_gb
132
+
133
+ # Storage analysis if requested
134
+ if include_storage:
135
+ try:
136
+ storage_response = rds.describe_db_instances(DBInstanceIdentifier=instance_id)
137
+ db_instance = storage_response["DBInstances"][0]
138
+ instance_data["StorageEncrypted"] = db_instance.get("StorageEncrypted", False)
139
+ instance_data["Iops"] = db_instance.get("Iops", "Standard")
140
+ except ClientError as e:
141
+ logger.debug(f"Could not get storage details for {instance_id}: {e}")
142
+
143
+ # Cost optimization analysis
144
+ optimization_notes = []
145
+
146
+ # Low CPU utilization suggests downsizing
147
+ if cpu_avg < 20:
148
+ optimization_notes.append(f"Low CPU utilization ({cpu_avg}%)")
149
+ optimization_candidates.append(instance_id)
150
+
151
+ # High memory availability suggests downsizing
152
+ if mem_avg_gb > 2: # More than 2GB free on average
153
+ optimization_notes.append(f"High free memory ({mem_avg_gb}GB avg)")
154
+
155
+ # Reserved instance recommendation
156
+ if purchase_type == "On-Demand" and cpu_avg > 10: # Active instance
157
+ optimization_notes.append("Consider Reserved Instance")
158
+
159
+ instance_data["OptimizationNotes"] = "; ".join(optimization_notes) if optimization_notes else "None"
160
+ instance_data["OptimizationCandidate"] = instance_id in optimization_candidates
161
+
162
+ data.append(instance_data)
163
+
164
+ # Log summary for this instance
165
+ logger.info(f" → {instance_class}, CPU: {cpu_avg}%, Free Mem: {mem_avg_gb}GB, {purchase_type}")
166
+
167
+ # Export results
168
+ write_to_csv(data, output_file)
169
+ logger.info(f"RDS analysis exported to: {output_file}")
170
+
171
+ # Summary report
172
+ logger.info("\n=== ANALYSIS SUMMARY ===")
173
+ logger.info(f"Total RDS instances: {len(instances)}")
174
+ logger.info(f"Reserved instances: {sum(1 for d in data if d['PurchaseType'] == 'Reserved')}")
175
+ logger.info(f"On-Demand instances: {sum(1 for d in data if d['PurchaseType'] == 'On-Demand')}")
176
+ logger.info(f"Optimization candidates: {len(optimization_candidates)}")
177
+
178
+ if optimization_candidates:
179
+ logger.warning(f"⚠ Instances with low utilization ({days}d avg):")
180
+ for instance_id in optimization_candidates:
181
+ instance_info = next(d for d in data if d["InstanceIdentifier"] == instance_id)
182
+ cpu_avg = instance_info[f"CPUUtilization_Avg_{days}d"]
183
+ logger.warning(f" - {instance_id}: {cpu_avg}% CPU")
184
+ else:
185
+ logger.info("✓ No obvious optimization candidates found")
186
+
187
+ # Engine distribution
188
+ engines = {}
189
+ for instance in data:
190
+ engine = instance["Engine"]
191
+ engines[engine] = engines.get(engine, 0) + 1
192
+
193
+ logger.info("Engine distribution:")
194
+ for engine, count in sorted(engines.items()):
195
+ logger.info(f" {engine}: {count} instances")
196
+
197
+ except Exception as e:
198
+ logger.error(f"Failed to analyze RDS instances: {e}")
199
+ raise