runbooks 0.7.0__py3-none-any.whl → 0.7.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 (132) 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.6.dist-info/METADATA +608 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
  69. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
  70. jupyter-agent/.env +0 -2
  71. jupyter-agent/.env.template +0 -2
  72. jupyter-agent/.gitattributes +0 -35
  73. jupyter-agent/.gradio/certificate.pem +0 -31
  74. jupyter-agent/README.md +0 -16
  75. jupyter-agent/__main__.log +0 -8
  76. jupyter-agent/app.py +0 -256
  77. jupyter-agent/cloudops-agent.png +0 -0
  78. jupyter-agent/ds-system-prompt.txt +0 -154
  79. jupyter-agent/jupyter-agent.png +0 -0
  80. jupyter-agent/llama3_template.jinja +0 -123
  81. jupyter-agent/requirements.txt +0 -9
  82. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
  83. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
  84. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
  85. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
  86. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
  87. jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
  88. jupyter-agent/utils.py +0 -409
  89. runbooks/aws/__init__.py +0 -58
  90. runbooks/aws/dynamodb_operations.py +0 -231
  91. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  92. runbooks/aws/ec2_describe_instances.py +0 -202
  93. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  94. runbooks/aws/ec2_run_instances.py +0 -213
  95. runbooks/aws/ec2_start_stop_instances.py +0 -212
  96. runbooks/aws/ec2_terminate_instances.py +0 -143
  97. runbooks/aws/ec2_unused_eips.py +0 -196
  98. runbooks/aws/ec2_unused_volumes.py +0 -188
  99. runbooks/aws/s3_create_bucket.py +0 -142
  100. runbooks/aws/s3_list_buckets.py +0 -152
  101. runbooks/aws/s3_list_objects.py +0 -156
  102. runbooks/aws/s3_object_operations.py +0 -183
  103. runbooks/aws/tagging_lambda_handler.py +0 -183
  104. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  105. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  106. runbooks/inventory/aws_organization.png +0 -0
  107. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  108. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  109. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  110. runbooks/inventory/update_aws_actions.py +0 -173
  111. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  112. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  113. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  114. runbooks/inventory/update_s3_public_access_block.py +0 -539
  115. runbooks/organizations/__init__.py +0 -12
  116. runbooks/organizations/manager.py +0 -374
  117. runbooks-0.7.0.dist-info/METADATA +0 -375
  118. /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
  119. /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
  120. /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
  121. /runbooks/inventory/{tests → Tests}/setup.py +0 -0
  122. /runbooks/inventory/{tests → Tests}/src.py +0 -0
  123. /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
  124. /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
  125. /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
  126. /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
  127. /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
  128. /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
  129. /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
  130. /runbooks/{aws → operate}/tags.json +0 -0
  131. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
  132. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +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