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
@@ -1,196 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- Find and Report Unused Elastic IPs (EIPs) via AWS SES.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-07
8
- Version: 2.0.0
9
-
10
- Description:
11
- - Identifies unused Elastic IPs in AWS.
12
- - Sends details via email using AWS Simple Email Service (SES).
13
-
14
- Requirements:
15
- - IAM Role Permissions:
16
- * ec2:DescribeAddresses
17
- * ses:SendEmail
18
- - Environment Variables:
19
- * SOURCE_EMAIL: Sender email address verified in SES.
20
- * DEST_EMAIL: Recipient email address.
21
- """
22
-
23
- import json
24
- import logging
25
- import os
26
- from typing import Dict, List
27
-
28
- import boto3
29
- from botocore.exceptions import BotoCoreError, ClientError
30
-
31
- from runbooks.utils.logger import configure_logger
32
-
33
- ## ✅ Configure Logger
34
- logger = configure_logger(__name__)
35
-
36
-
37
- # ==============================
38
- # AWS CLIENT INITIALIZATION
39
- # ==============================
40
- def get_boto3_clients():
41
- """
42
- Initializes AWS clients for EC2 and SES.
43
-
44
- Returns:
45
- Tuple[boto3.client, boto3.client]: EC2 and SES clients.
46
- """
47
- ec2_client = boto3.client("ec2")
48
- ses_client = boto3.client("ses")
49
- return ec2_client, ses_client
50
-
51
-
52
- # ==============================
53
- # CONFIGURATION VARIABLES
54
- # ==============================
55
- def load_environment_variables():
56
- """
57
- Loads and validates environment variables required for execution.
58
-
59
- Returns:
60
- Tuple[str, str]: Source and destination email addresses.
61
- """
62
- source_email = os.getenv("SOURCE_EMAIL")
63
- dest_email = os.getenv("DEST_EMAIL")
64
-
65
- if not source_email or not dest_email:
66
- raise ValueError("Environment variables SOURCE_EMAIL and DEST_EMAIL must be set.")
67
-
68
- return source_email, dest_email
69
-
70
-
71
- # ==============================
72
- # EIP UTILITIES
73
- # ==============================
74
- def get_unused_eips(ec2_client) -> List[Dict[str, str]]:
75
- """
76
- Fetches unused Elastic IPs (EIPs).
77
-
78
- Args:
79
- ec2_client (boto3.client): EC2 client.
80
-
81
- Returns:
82
- List[Dict[str, str]]: List of unused EIPs with details.
83
- """
84
- try:
85
- response = ec2_client.describe_addresses()
86
- unused_eips = []
87
-
88
- for address in response["Addresses"]:
89
- if "InstanceId" not in address: ## Not associated with an instance
90
- unused_eips.append(
91
- {
92
- "PublicIp": address["PublicIp"],
93
- "AllocationId": address["AllocationId"],
94
- "Domain": address.get("Domain", "N/A"), ## VPC or standard
95
- }
96
- )
97
-
98
- logger.info(f"Found {len(unused_eips)} unused EIPs.")
99
- return unused_eips
100
-
101
- except ClientError as e:
102
- logger.error(f"Failed to describe addresses: {e.response['Error']['Code']} - {e}")
103
- raise
104
- except BotoCoreError as e:
105
- logger.error(f"BotoCore error occurred: {e}")
106
- raise
107
-
108
-
109
- # ==============================
110
- # EMAIL UTILITIES
111
- # ==============================
112
- def format_eip_report(eips: List[Dict[str, str]]) -> str:
113
- """
114
- Formats the EIPs data as a markdown table for email reporting.
115
-
116
- Args:
117
- eips (List[Dict[str, str]]): List of unused EIPs.
118
-
119
- Returns:
120
- str: Formatted markdown table.
121
- """
122
- if not eips:
123
- return "No unused EIPs found."
124
-
125
- ## Table Header
126
- table = "| Public IP | Allocation ID | Domain |\n|-----------|----------------|--------|\n"
127
-
128
- ## Table Rows
129
- for eip in eips:
130
- table += f"| {eip['PublicIp']} | {eip['AllocationId']} | {eip['Domain']} |\n"
131
-
132
- return table
133
-
134
-
135
- def send_email_report(ses_client, source_email: str, dest_email: str, eips: List[Dict[str, str]]) -> None:
136
- """
137
- Sends an email report via AWS SES.
138
-
139
- Args:
140
- ses_client (boto3.client): SES client.
141
- source_email (str): Sender email address.
142
- dest_email (str): Recipient email address.
143
- eips (List[Dict[str, str]]): List of unused EIPs.
144
- """
145
- try:
146
- subject = "AWS Report: Unused Elastic IPs (EIPs)"
147
- body = format_eip_report(eips)
148
-
149
- logger.info(f"Sending email from {source_email} to {dest_email}...")
150
- ses_client.send_email(
151
- Source=source_email,
152
- Destination={"ToAddresses": [dest_email]},
153
- Message={
154
- "Subject": {"Data": subject, "Charset": "utf-8"},
155
- "Body": {"Text": {"Data": body, "Charset": "utf-8"}},
156
- },
157
- )
158
- logger.info("Email sent successfully.")
159
-
160
- except ClientError as e:
161
- logger.error(f"Failed to send email: {e.response['Error']['Code']} - {e}")
162
- raise
163
- except BotoCoreError as e:
164
- logger.error(f"BotoCore error occurred: {e}")
165
- raise
166
-
167
-
168
- # ==============================
169
- # MAIN HANDLER
170
- # ==============================
171
- def lambda_handler(event, context):
172
- """
173
- AWS Lambda handler for reporting unused Elastic IPs.
174
-
175
- Args:
176
- event (dict): AWS event data.
177
- context: AWS Lambda context.
178
- """
179
- try:
180
- ## ✅ Load configurations
181
- source_email, dest_email = load_environment_variables()
182
-
183
- ## ✅ Initialize AWS clients
184
- ec2_client, ses_client = get_boto3_clients()
185
-
186
- ## ✅ Fetch unused EIPs
187
- unused_eips = get_unused_eips(ec2_client)
188
-
189
- ## ✅ Send email report using SES
190
- send_email_report(ses_client, source_email, dest_email, unused_eips)
191
-
192
- return {"statusCode": 200, "body": "Report sent successfully."}
193
-
194
- except Exception as e:
195
- logger.error(f"Error: {e}")
196
- return {"statusCode": 500, "body": str(e)}
@@ -1,188 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- AWS EC2 Unused Volume Checker with SNS Notification.
4
-
5
- Finds unattached EBS volumes and sends the details via SNS notification.
6
-
7
- Author: nnthanh101@gmail.com
8
- Date: 2025-01-08
9
- Version: 1.0.0
10
- """
11
-
12
- import json
13
- import logging
14
- import os
15
- import sys
16
- from typing import Dict, List
17
-
18
- import boto3
19
- from botocore.exceptions import BotoCoreError, ClientError
20
-
21
- from runbooks.utils.logger import configure_logger # Reusable logger utility
22
-
23
- ## ✅ Configure Logger
24
- logger = configure_logger(__name__)
25
-
26
- # ==============================
27
- # CONFIGURATION VARIABLES
28
- # ==============================
29
- AWS_REGION = os.getenv("AWS_REGION", "ap-southeast-2")
30
- SNS_TOPIC_ARN = os.getenv("SNS_TOPIC_ARN", "arn:aws:sns:ap-southeast-2:999999999999:1cloudops")
31
-
32
- # ==============================
33
- # AWS CLIENT INITIALIZATION
34
- # ==============================
35
- ec2_client = boto3.client("ec2", region_name=AWS_REGION)
36
- sns_client = boto3.client("sns", region_name=AWS_REGION)
37
-
38
-
39
- # ==============================
40
- # VALIDATION UTILITIES
41
- # ==============================
42
- def validate_sns_arn(arn: str) -> None:
43
- """
44
- Validates the format of the SNS Topic ARN.
45
-
46
- Args:
47
- arn (str): SNS Topic ARN.
48
-
49
- Raises:
50
- ValueError: If the ARN format is invalid.
51
- """
52
- if not arn.startswith("arn:aws:sns:"):
53
- raise ValueError(f"Invalid SNS Topic ARN: {arn}")
54
- logger.info(f"✅ Valid SNS ARN: {arn}")
55
-
56
-
57
- # ==============================
58
- # CORE FUNCTION: FIND UNUSED VOLUMES
59
- # ==============================
60
- def find_unused_volumes() -> List[Dict[str, str]]:
61
- """
62
- Identifies unused (unattached) EBS volumes in the AWS account.
63
-
64
- Returns:
65
- List[Dict[str, str]]: List of unused volumes with details.
66
- """
67
- try:
68
- ## ✅ Retrieve all volumes
69
- logger.info("🔍 Fetching all EBS volumes...")
70
- response = ec2_client.describe_volumes()
71
-
72
- ## ✅ Initialize Unused Volumes List
73
- unused_volumes = []
74
-
75
- ## ✅ Enhanced Loop with Debug Logs
76
- for vol in response["Volumes"]:
77
- if len(vol.get("Attachments", [])) == 0: ## Unattached volumes
78
- ## Log detailed info for debugging
79
- logger.debug(f"Unattached Volume: {json.dumps(vol, default=str)}")
80
-
81
- ## Append Volume Details
82
- unused_volumes.append(
83
- {
84
- "VolumeId": vol["VolumeId"],
85
- "Size": vol["Size"],
86
- "State": vol["State"],
87
- "Encrypted": vol.get("Encrypted", False),
88
- "VolumeType": vol.get("VolumeType", "unknown"),
89
- "CreateTime": str(vol["CreateTime"]),
90
- }
91
- )
92
-
93
- logger.info(f"✅ Found {len(unused_volumes)} unused volumes.")
94
- return unused_volumes
95
-
96
- except ClientError as e:
97
- logger.error(f"❌ AWS Client Error: {e}")
98
- raise
99
-
100
- except Exception as e:
101
- logger.error(f"❌ Unexpected error: {e}")
102
- raise
103
-
104
-
105
- # ==============================
106
- # NOTIFICATION FUNCTION: SEND EMAIL
107
- # ==============================
108
- def send_sns_notification(unused_volumes: List[Dict[str, str]]) -> None:
109
- """
110
- Sends unused EBS volume details via SNS notification.
111
-
112
- Args:
113
- unused_volumes (List[Dict[str, str]]): List of unused volumes.
114
-
115
- Raises:
116
- Exception: If SNS publish fails.
117
- """
118
- try:
119
- ## ✅ Prepare Email Body (Markdown for Readability)
120
- email_body = "### Unused EBS Volumes Report 📊\n\n"
121
- email_body += "| VolumeId | Size (GiB) | State | Encrypted | VolumeType | CreateTime |\n"
122
- email_body += "|----------|------------|-------|-----------|------------|------------|\n"
123
- for vol in unused_volumes:
124
- email_body += f"| {vol['VolumeId']} | {vol['Size']} | {vol['State']} | {vol['Encrypted']} | {vol['VolumeType']} | {vol['CreateTime']} |\n"
125
-
126
- ## ✅ Publish to SNS
127
- logger.info(f"Sending notification to SNS topic: {SNS_TOPIC_ARN}...")
128
- logger.info(f"📤 Sending SNS notification to SNS topic: {SNS_TOPIC_ARN}")
129
- sns_client.publish(
130
- TopicArn=SNS_TOPIC_ARN,
131
- Subject="Unused EBS Volumes Report",
132
- Message=email_body,
133
- )
134
- logger.info("✅ SNS notification sent successfully.")
135
-
136
- except ClientError as e:
137
- logger.error(f"❌ SNS Client Error: {e}")
138
- raise
139
-
140
- except Exception as e:
141
- logger.error(f"❌ Unexpected error while sending SNS notification: {e}")
142
- raise
143
-
144
-
145
- # ==============================
146
- # MAIN FUNCTION
147
- # ==============================
148
- def main() -> None:
149
- """
150
- Main function to find unused volumes and send notifications.
151
- """
152
- try:
153
- ## ✅ Validate Inputs/Configuration
154
- validate_sns_arn(SNS_TOPIC_ARN)
155
-
156
- ## ✅ Find Unused Volumes
157
- unused_volumes = find_unused_volumes()
158
-
159
- if unused_volumes:
160
- ## ✅ Send SNS Notification if unused volumes exist
161
- send_sns_notification(unused_volumes)
162
- else:
163
- logger.info("⚠️ No unused volumes found. Exiting without notification.")
164
-
165
- except Exception as e:
166
- logger.error(f"❌ Fatal Error: {e}")
167
- sys.exit(1)
168
-
169
-
170
- # ==============================
171
- # LAMBDA HANDLER
172
- # ==============================
173
- def lambda_handler(event, context):
174
- """
175
- AWS Lambda Handler for unused EBS volume detection and notification.
176
-
177
- Args:
178
- event (dict): AWS Lambda event.
179
- context: AWS Lambda context.
180
- """
181
- main() # Reuse the main function for Lambda
182
-
183
-
184
- # ==============================
185
- # ENTRY POINT
186
- # ==============================
187
- if __name__ == "__main__":
188
- main()
@@ -1,142 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- AWS S3 Bucket Creator Script.
4
-
5
- Author: nnthanh101@gmail.com
6
- Date: 2025-01-09
7
- Version: 1.0.0
8
- """
9
-
10
- import os
11
- import sys
12
- from typing import Optional
13
-
14
- import boto3
15
- from botocore.exceptions import BotoCoreError, ClientError
16
-
17
- from runbooks.utils.logger import configure_logger # # Import reusable logger
18
-
19
- ## Initialize Logger
20
- logger = configure_logger("list_s3_buckets")
21
-
22
- # ==============================
23
- # CONFIGURATION VARIABLES
24
- # ==============================
25
- DEFAULT_BUCKET_NAME = os.getenv("S3_BUCKET_NAME", "1cloudops") # Default bucket name
26
- DEFAULT_REGION = os.getenv("AWS_REGION", "ap-southeast-2") # Default AWS region
27
-
28
-
29
- # ==============================
30
- # VALIDATION UTILITIES
31
- # ==============================
32
- def validate_bucket_name(bucket_name: str) -> None:
33
- """
34
- Validates an S3 bucket name based on AWS naming rules.
35
-
36
- Args:
37
- bucket_name (str): The bucket name to validate.
38
-
39
- Raises:
40
- ValueError: If the bucket name is invalid.
41
- """
42
- import re
43
-
44
- ## ✅ AWS Bucket Naming Rules
45
- if len(bucket_name) < 3 or len(bucket_name) > 63:
46
- raise ValueError("Bucket name must be between 3 and 63 characters long.")
47
-
48
- if not re.match(r"^[a-z0-9.-]+$", bucket_name):
49
- raise ValueError("Bucket name can only contain lowercase letters, numbers, hyphens (-), and periods (.).")
50
-
51
- if bucket_name.startswith(".") or bucket_name.endswith("."):
52
- raise ValueError("Bucket name cannot start or end with a period (.)")
53
-
54
- if ".." in bucket_name:
55
- raise ValueError("Bucket name cannot contain consecutive periods (..).")
56
-
57
- logger.info(f"✅ Bucket name '{bucket_name}' is valid.")
58
-
59
-
60
- # ==============================
61
- # CORE FUNCTION: CREATE BUCKET
62
- # ==============================
63
- def create_s3_bucket(bucket_name: str, region: str) -> Optional[str]:
64
- """
65
- Creates an S3 bucket in the specified AWS region.
66
-
67
- Args:
68
- bucket_name (str): The name of the S3 bucket to create.
69
- region (str): The AWS region where the bucket will be created.
70
-
71
- Returns:
72
- Optional[str]: The location of the created bucket if successful, None otherwise.
73
-
74
- Raises:
75
- Exception: Raises error if bucket creation fails.
76
- """
77
- ## ✅ Initialize S3 Client
78
- try:
79
- s3_client = boto3.client("s3", region_name=region)
80
- logger.info(f"Creating bucket '{bucket_name}' in region '{region}'...")
81
-
82
- ## ✅ Create bucket with LocationConstraint
83
- if region == "us-east-1": ## Special case: us-east-1 doesn't require LocationConstraint
84
- response = s3_client.create_bucket(
85
- Bucket=bucket_name,
86
- ACL="private", ## Set access control to private
87
- )
88
- else:
89
- response = s3_client.create_bucket(
90
- Bucket=bucket_name,
91
- ACL="private",
92
- CreateBucketConfiguration={"LocationConstraint": region},
93
- )
94
-
95
- logger.info(f"✅ Bucket '{bucket_name}' created successfully at {response['Location']}.")
96
- return response["Location"]
97
-
98
- except ClientError as e:
99
- logger.error(f"❌ AWS Client Error: {e}")
100
- raise
101
-
102
- except BotoCoreError as e:
103
- logger.error(f"❌ BotoCore Error: {e}")
104
- raise
105
-
106
- except Exception as e:
107
- logger.error(f"❌ Unexpected error: {e}")
108
- raise
109
-
110
-
111
- # ==============================
112
- # MAIN FUNCTION
113
- # ==============================
114
- def main() -> None:
115
- """
116
- Main entry point for script execution.
117
- """
118
- try:
119
- ## ✅ Parse Arguments or Use Environment Variables
120
- bucket_name = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_BUCKET_NAME
121
- region = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_REGION
122
-
123
- ## ✅ Validate Input
124
- validate_bucket_name(bucket_name)
125
-
126
- ## ✅ Create S3 Bucket
127
- create_s3_bucket(bucket_name, region)
128
-
129
- except ValueError as e:
130
- logger.error(f"❌ Input Validation Error: {e}")
131
- sys.exit(1)
132
-
133
- except Exception as e:
134
- logger.error(f"❌ Fatal Error: {e}")
135
- sys.exit(1)
136
-
137
-
138
- # ==============================
139
- # ENTRY POINT
140
- # ==============================
141
- if __name__ == "__main__":
142
- main()
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- AWS S3 Bucket Listing Utility with logging and error handling.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-05
8
- Version: 1.0.0
9
-
10
- Description:
11
- This script lists all S3 buckets in the AWS account using the Boto3 library.
12
- It implements robust error handling, logging, and modularization for high standards.
13
-
14
- Usage:
15
- python list_s3_buckets.py
16
- """
17
-
18
- import json
19
- import os
20
- from typing import Dict, List
21
-
22
- import boto3
23
- from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError
24
- from tabulate import tabulate
25
-
26
- from runbooks.utils.logger import configure_logger
27
-
28
- ## Initialize Logger
29
- logger = configure_logger("list_s3_buckets")
30
-
31
-
32
- def get_s3_client(region: str = None) -> boto3.client:
33
- """
34
- Initializes and returns a Boto3 S3 client with optional region support.
35
-
36
- Args:
37
- region (str, optional): AWS region. Defaults to None (uses environment or AWS config).
38
-
39
- Returns:
40
- boto3.client: Configured S3 client.
41
-
42
- Raises:
43
- NoCredentialsError: Raised if AWS credentials are missing.
44
- PartialCredentialsError: Raised if AWS credentials are incomplete.
45
- """
46
- try:
47
- ## ✅ Allow region override if specified
48
- session = boto3.Session(region_name=region) if region else boto3.Session()
49
- client = session.client("s3")
50
- logger.info("S3 client initialized successfully.")
51
- return client
52
- except (NoCredentialsError, PartialCredentialsError) as e:
53
- # logger.error("Please configure them using AWS CLI or environment variables.")
54
- logger.error(f"AWS Credentials Error: {str(e)}")
55
- raise
56
- except Exception as e:
57
- logger.error(f"Unexpected error: {str(e)}")
58
- raise
59
-
60
-
61
- def list_s3_buckets(client: boto3.client) -> List[Dict[str, str]]:
62
- """
63
- Lists all S3 buckets in the AWS account.
64
-
65
- Args:
66
- client (boto3.client): Pre-configured S3 client.
67
-
68
- Returns:
69
- List[Dict[str, str]]: List of bucket details including name and creation date.
70
-
71
- Raises:
72
- ClientError: For API errors returned by AWS.
73
- """
74
- try:
75
- ## ✅ Call AWS API to list buckets
76
- response = client.list_buckets()
77
-
78
- ## ✅ Extract bucket names and creation dates
79
- # bucket_list = [{"Name": bucket['Name'], "CreationDate": str(bucket['CreationDate'])} for bucket in buckets]
80
- bucket_list = [
81
- {
82
- "Name": bucket["Name"],
83
- "CreationDate": bucket["CreationDate"].strftime("%Y-%m-%d %H:%M:%S"),
84
- "Owner": {
85
- "DisplayName": response["Owner"].get("DisplayName", "N/A"),
86
- # "ID": response['Owner'].get('ID', 'N/A')
87
- },
88
- }
89
- for bucket in response.get("Buckets", [])
90
- ]
91
-
92
- ## ✅ Log the number of buckets found
93
- if not bucket_list:
94
- logger.warning("No buckets found.")
95
- else:
96
- logger.info(f"Found {len(bucket_list)} S3 bucket(s).")
97
-
98
- return bucket_list
99
- except ClientError as e:
100
- logger.error(f"Failed to list buckets: {e.response['Error']['Message']}")
101
- raise
102
- except Exception as e:
103
- logger.error(f"Unexpected error: {str(e)}")
104
- raise
105
-
106
-
107
- def display_buckets(buckets: List[Dict[str, str]]) -> None:
108
- """
109
- Displays bucket details in JSON format for readability.
110
-
111
- Args:
112
- buckets (List[Dict[str, str]]): List of bucket details.
113
- """
114
-
115
- # print(json.dumps(buckets, indent=4)) if buckets else print("No buckets found.")
116
- ## ✅ Prepare Table Headers and Rows
117
- headers = ["Name", "Creation Date", "Owner Display Name", "Owner ID"]
118
- rows = [
119
- # [bucket["Name"], bucket["CreationDate"], bucket["Owner"]["DisplayName"], bucket["Owner"]["ID"]]
120
- [bucket["Name"], bucket["CreationDate"], bucket["Owner"]["DisplayName"]]
121
- for bucket in buckets
122
- ]
123
-
124
- ## ✅ Render Markdown Table
125
- print("### AWS S3 Buckets\n")
126
- ## Creating & printing the Markdown Table
127
- table = tabulate(rows, headers=headers, tablefmt="github", missingval="N/A")
128
- print(table)
129
-
130
-
131
- ## ==============================
132
- ## MAIN FUNCTION
133
- ## ==============================
134
- def main() -> None:
135
- """
136
- Main entry point for listing S3 buckets.
137
- """
138
- try:
139
- ## ✅ Load AWS region dynamically (fallback to default)
140
- region = os.getenv("AWS_REGION", "us-east-1")
141
- ## ✅ Initialize S3 client
142
- client = get_s3_client(region)
143
- ## ✅ Retrieve bucket list
144
- buckets = list_s3_buckets(client)
145
- ## ✅ Display results
146
- display_buckets(buckets)
147
- except Exception as e:
148
- logger.error(f"Program terminated with error: {str(e)}")
149
-
150
-
151
- if __name__ == "__main__":
152
- main()