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.
- runbooks/__init__.py +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
"""
|
2
|
+
DynamoDB Cost Optimization - Analyze and optimize table settings for cost savings.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from datetime import datetime, timedelta
|
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, list_tables, write_to_csv
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
@lru_cache(maxsize=32)
|
18
|
+
def get_table_details(table_name):
|
19
|
+
"""Get DynamoDB table details with error handling."""
|
20
|
+
try:
|
21
|
+
dynamodb = get_client("dynamodb")
|
22
|
+
return dynamodb.describe_table(TableName=table_name)["Table"]
|
23
|
+
except ClientError as e:
|
24
|
+
logger.error(f"Failed to get table details for {table_name}: {e}")
|
25
|
+
raise
|
26
|
+
|
27
|
+
|
28
|
+
def update_table_billing_mode(table_name, billing_mode):
|
29
|
+
"""Update DynamoDB table billing mode."""
|
30
|
+
try:
|
31
|
+
dynamodb = get_client("dynamodb")
|
32
|
+
dynamodb.update_table(TableName=table_name, BillingMode=billing_mode)
|
33
|
+
logger.info(f"Updated {table_name} to {billing_mode} billing mode")
|
34
|
+
except ClientError as e:
|
35
|
+
logger.error(f"Failed to update {table_name} billing mode: {e}")
|
36
|
+
raise
|
37
|
+
|
38
|
+
|
39
|
+
@lru_cache(maxsize=32)
|
40
|
+
def analyze_table(table_name):
|
41
|
+
"""Analyze table usage patterns and suggest cost optimizations."""
|
42
|
+
try:
|
43
|
+
table_details = get_table_details(table_name)
|
44
|
+
|
45
|
+
# Get basic table info
|
46
|
+
billing_mode_summary = table_details.get("BillingModeSummary", {})
|
47
|
+
billing_mode = billing_mode_summary.get("BillingMode", "PROVISIONED")
|
48
|
+
table_size_bytes = table_details.get("TableSizeBytes", 0)
|
49
|
+
table_item_count = table_details.get("ItemCount", 0)
|
50
|
+
|
51
|
+
# Get CloudWatch metrics for last 7 days
|
52
|
+
end_time = datetime.utcnow()
|
53
|
+
start_time = end_time - timedelta(days=7)
|
54
|
+
|
55
|
+
try:
|
56
|
+
cloudwatch = get_client("cloudwatch")
|
57
|
+
metrics = cloudwatch.get_metric_statistics(
|
58
|
+
Namespace="AWS/DynamoDB",
|
59
|
+
MetricName="ConsumedReadCapacityUnits",
|
60
|
+
Dimensions=[{"Name": "TableName", "Value": table_name}],
|
61
|
+
StartTime=start_time,
|
62
|
+
EndTime=end_time,
|
63
|
+
Period=86400, # Daily resolution
|
64
|
+
Statistics=["Average"],
|
65
|
+
)
|
66
|
+
|
67
|
+
# Calculate average RCU usage
|
68
|
+
datapoints = metrics.get("Datapoints", [])
|
69
|
+
average_rcu = sum(dp["Average"] for dp in datapoints) / len(datapoints) if datapoints else 0
|
70
|
+
|
71
|
+
except ClientError as e:
|
72
|
+
logger.warning(f"Could not get CloudWatch metrics for {table_name}: {e}")
|
73
|
+
average_rcu = 0
|
74
|
+
|
75
|
+
# Get provisioned capacity if applicable
|
76
|
+
provisioned_rcu = None
|
77
|
+
if billing_mode == "PROVISIONED" and "ProvisionedThroughput" in table_details:
|
78
|
+
provisioned_rcu = table_details["ProvisionedThroughput"].get("ReadCapacityUnits", 0)
|
79
|
+
|
80
|
+
# Build analysis result
|
81
|
+
analysis = {
|
82
|
+
"Table": table_name,
|
83
|
+
"Billing Mode": billing_mode,
|
84
|
+
"Table Size": f"{table_size_bytes / (1024**3):.2f} GB" if table_size_bytes else "0 GB",
|
85
|
+
"Item Count": table_item_count,
|
86
|
+
"Average RCU": round(average_rcu, 2),
|
87
|
+
"Provisioned RCU": provisioned_rcu,
|
88
|
+
"Recommendations": [],
|
89
|
+
}
|
90
|
+
|
91
|
+
# Generate recommendations
|
92
|
+
if billing_mode == "PROVISIONED" and provisioned_rcu and average_rcu < 0.9 * provisioned_rcu:
|
93
|
+
analysis["Recommendations"].append("Consider lowering provisioned RCU to match actual usage")
|
94
|
+
|
95
|
+
if average_rcu > 1000:
|
96
|
+
analysis["Recommendations"].append("Consider enabling DynamoDB Accelerator (DAX) for high read workloads")
|
97
|
+
|
98
|
+
# Storage optimization for large, infrequently accessed tables
|
99
|
+
if table_size_bytes > 10 * (1024**3) and table_item_count > 0 and average_rcu / table_item_count < 0.1:
|
100
|
+
table_class = table_details.get("TableClassSummary", {}).get("TableClass", "STANDARD")
|
101
|
+
if table_class != "STANDARD_INFREQUENT_ACCESS":
|
102
|
+
analysis["Recommendations"].append("Consider STANDARD_INFREQUENT_ACCESS for storage cost savings")
|
103
|
+
|
104
|
+
return analysis
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
logger.error(f"Failed to analyze table {table_name}: {e}")
|
108
|
+
return {
|
109
|
+
"Table": table_name,
|
110
|
+
"Error": str(e),
|
111
|
+
"Recommendations": ["Unable to analyze - check table permissions"],
|
112
|
+
}
|
113
|
+
|
114
|
+
|
115
|
+
@click.command()
|
116
|
+
@click.option("--apply", "-a", is_flag=True, help="Apply suggested optimizations")
|
117
|
+
@click.option("--output", "-o", default="/tmp/dynamodb_analysis.csv", help="Output CSV file path")
|
118
|
+
def dynamodb_analyze(apply=False, output="/tmp/dynamodb_analysis.csv"):
|
119
|
+
"""Analyze DynamoDB tables and suggest cost optimizations."""
|
120
|
+
logger.info(f"Analyzing DynamoDB tables in {display_aws_account_info()}")
|
121
|
+
|
122
|
+
try:
|
123
|
+
table_names = list_tables()
|
124
|
+
if not table_names:
|
125
|
+
logger.info("No DynamoDB tables found")
|
126
|
+
return
|
127
|
+
|
128
|
+
logger.info(f"Found {len(table_names)} tables to analyze")
|
129
|
+
|
130
|
+
# Analyze each table
|
131
|
+
results = []
|
132
|
+
for table_name in table_names:
|
133
|
+
logger.info(f"Analyzing table: {table_name}")
|
134
|
+
result = analyze_table(table_name)
|
135
|
+
results.append(result)
|
136
|
+
|
137
|
+
# Log recommendations
|
138
|
+
if result.get("Recommendations"):
|
139
|
+
for rec in result["Recommendations"]:
|
140
|
+
logger.info(f" Recommendation: {rec}")
|
141
|
+
else:
|
142
|
+
logger.info(" No optimization recommendations")
|
143
|
+
|
144
|
+
# Save results to CSV
|
145
|
+
write_to_csv(results, output)
|
146
|
+
logger.info(f"Analysis results saved to: {output}")
|
147
|
+
|
148
|
+
# Apply optimizations if requested
|
149
|
+
if apply:
|
150
|
+
logger.info("Apply functionality not yet implemented")
|
151
|
+
# TODO: Implement safe optimization application
|
152
|
+
|
153
|
+
except Exception as e:
|
154
|
+
logger.error(f"Failed to analyze DynamoDB tables: {e}")
|
155
|
+
raise
|