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,156 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- List all objects inside a specified S3 bucket with logging and error handling.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-06
8
- Version: 1.0.0
9
-
10
- Usage:
11
- python list_s3_objects.py <bucket_name>
12
- """
13
-
14
- import argparse
15
- import sys
16
- from typing import Dict, List, Optional
17
-
18
- import boto3
19
- from botocore.exceptions import BotoCoreError, ClientError, NoCredentialsError
20
- from tabulate import tabulate
21
-
22
- from runbooks.utils.logger import configure_logger
23
-
24
- ## ✅ Configure Logger
25
- logger = configure_logger(__name__)
26
-
27
-
28
- ## ==============================
29
- ## AWS S3 UTILITIES
30
- ## ==============================
31
- def s3_list_objects(
32
- bucket_name: str,
33
- prefix: Optional[str] = None,
34
- max_keys: int = 1000,
35
- ) -> List[Dict[str, str]]:
36
- """
37
- List objects in the specified S3 bucket.
38
-
39
- Args:
40
- bucket_name (str): The name of the S3 bucket.
41
- prefix (Optional[str]): Filter objects by prefix (default: None).
42
- max_keys (int): Maximum number of keys to retrieve per request (default: 1000).
43
-
44
- Returns:
45
- List[Dict[str, str]]: List of object details, including key, size, and last modified date.
46
-
47
- Raises:
48
- NoCredentialsError: If AWS credentials are missing.
49
- ClientError: If there is an issue accessing the bucket.
50
- """
51
- try:
52
- logger.info(f"Initializing S3 client for bucket: {bucket_name}")
53
- client = boto3.client("s3")
54
-
55
- ## ✅ Prepare Parameters
56
- params = {"Bucket": bucket_name, "MaxKeys": max_keys}
57
- if prefix:
58
- params["Prefix"] = prefix
59
-
60
- ## ✅ Fetch Objects with Pagination Support
61
- paginator = client.get_paginator("list_objects_v2")
62
- page_iterator = paginator.paginate(**params)
63
-
64
- object_list = []
65
- for page in page_iterator:
66
- if "Contents" in page: # Check if there are objects
67
- for obj in page["Contents"]:
68
- object_list.append(
69
- {
70
- "Key": obj["Key"],
71
- "Size (KB)": f"{obj['Size'] / 1024:.2f}", # Convert bytes to KB
72
- "LastModified": obj["LastModified"].strftime("%Y-%m-%d %H:%M:%S"),
73
- }
74
- )
75
-
76
- ## ✅ Log Results
77
- logger.info(f"Found {len(object_list)} object(s) in bucket '{bucket_name}'.")
78
- return object_list
79
-
80
- except NoCredentialsError:
81
- logger.error("AWS credentials not found. Ensure ~/.aws/credentials is configured.")
82
- raise
83
-
84
- except ClientError as e:
85
- logger.error(f"AWS Client Error: {e}")
86
- raise
87
-
88
- except BotoCoreError as e:
89
- logger.error(f"BotoCore Error: {e}")
90
- raise
91
-
92
- except Exception as e:
93
- logger.error(f"Unexpected error: {e}")
94
- raise
95
-
96
-
97
- ## ==============================
98
- ## DISPLAY UTILITIES
99
- ## ==============================
100
- def display_objects(objects: List[Dict[str, str]], bucket_name: str) -> None:
101
- """
102
- Displays S3 object details in Markdown table format.
103
-
104
- Args:
105
- objects (List[Dict[str, str]]): List of S3 object details.
106
- bucket_name (str): Name of the S3 bucket.
107
- """
108
- if not objects:
109
- print(f"No objects found in bucket: {bucket_name}")
110
- return
111
-
112
- ## ✅ Prepare Table Headers and Rows
113
- headers = ["Key", "Size (KB)", "Last Modified"]
114
- rows = [[obj["Key"], obj["Size (KB)"], obj["LastModified"]] for obj in objects]
115
-
116
- ## ✅ Render Markdown Table
117
- print(f"### S3 Objects in Bucket: `{bucket_name}`\n")
118
- print(tabulate(rows, headers=headers, tablefmt="github"))
119
-
120
-
121
- ## ==============================
122
- ## MAIN FUNCTION
123
- ## ==============================
124
- def main():
125
- """
126
- Main entry point for listing S3 objects.
127
- """
128
- ## ✅ Parse Command-Line Arguments
129
- parser = argparse.ArgumentParser(description="List objects in an AWS S3 bucket.")
130
- parser.add_argument("--bucket", required=True, help="The name of the S3 bucket.")
131
- parser.add_argument("--prefix", default=None, help="Filter objects by prefix.")
132
- parser.add_argument(
133
- "--max-keys",
134
- type=int,
135
- default=1000,
136
- help="Max number of keys to fetch (default: 1000).",
137
- )
138
-
139
- args = parser.parse_args()
140
-
141
- try:
142
- ## ✅ Fetch and Display S3 Objects
143
- objects = s3_list_objects(
144
- bucket_name=args.bucket,
145
- prefix=args.prefix,
146
- max_keys=args.max_keys,
147
- )
148
- display_objects(objects, args.bucket)
149
-
150
- except Exception as e:
151
- logger.error(f"Program terminated with error: {e}")
152
- sys.exit(1)
153
-
154
-
155
- if __name__ == "__main__":
156
- main()
@@ -1,183 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- S3 Object Operations: Upload and Delete Objects in Amazon S3.
5
-
6
- This script provides functionality to:
7
- 1. Upload a file to an S3 bucket.
8
- 2. Delete a file from an S3 bucket.
9
-
10
- Designed for usage in Python (pip), Docker, and AWS Lambda environments.
11
-
12
- Author: nnthanh101@gmail.com
13
- Date: 2025-01-08
14
- Version: 1.0.0
15
- """
16
-
17
- import logging
18
- import os
19
- from typing import Optional
20
-
21
- import boto3
22
- from botocore.exceptions import BotoCoreError, ClientError
23
-
24
- # ==============================
25
- # CONFIGURATION VARIABLES
26
- # ==============================
27
- AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
28
- S3_BUCKET = os.getenv("S3_BUCKET", "my-default-bucket")
29
- S3_KEY = os.getenv("S3_KEY", "default-key.txt")
30
- LOCAL_FILE_PATH = os.getenv("LOCAL_FILE_PATH", "default.txt")
31
- ACL = os.getenv("ACL", "private") ## Options: 'private', 'public-read', etc.
32
-
33
- # ==============================
34
- # LOGGING CONFIGURATION
35
- # ==============================
36
- logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO)
37
- logger = logging.getLogger(__name__)
38
-
39
- # ==============================
40
- # AWS CLIENT INITIALIZATION
41
- # ==============================
42
- try:
43
- s3_client = boto3.client("s3", region_name=AWS_REGION)
44
- logger.info("✅ S3 client initialized successfully.")
45
- except Exception as e:
46
- logger.error(f"❌ Failed to initialize S3 client: {e}")
47
- raise
48
-
49
-
50
- # ==============================
51
- # UPLOAD FUNCTION
52
- # ==============================
53
- def put_object(bucket: str, key: str, file_path: str, acl: str = "private") -> None:
54
- """
55
- Uploads a file to an S3 bucket.
56
-
57
- Args:
58
- bucket (str): The name of the S3 bucket.
59
- key (str): The object key in S3.
60
- file_path (str): Local file path to be uploaded.
61
- acl (str): Access control list (default: 'private').
62
-
63
- Raises:
64
- Exception: Any upload failure.
65
- """
66
- try:
67
- # ✅ Check if the file exists locally
68
- if not os.path.exists(file_path):
69
- raise FileNotFoundError(f"File '{file_path}' not found.")
70
-
71
- logger.info(f"🚀 Uploading '{file_path}' to bucket '{bucket}' with key '{key}'...")
72
- with open(file_path, "rb") as file_reader:
73
- s3_client.put_object(ACL=acl, Body=file_reader, Bucket=bucket, Key=key)
74
- logger.info(f"✅ File '{file_path}' uploaded successfully to '{bucket}/{key}'.")
75
-
76
- except FileNotFoundError as e:
77
- logger.error(f"❌ File Not Found: {e}")
78
- raise
79
-
80
- except ClientError as e:
81
- logger.error(f"❌ AWS Client Error: {e}")
82
- raise
83
-
84
- except BotoCoreError as e:
85
- logger.error(f"❌ BotoCore Error: {e}")
86
- raise
87
-
88
- except Exception as e:
89
- logger.error(f"❌ Unexpected Error: {e}")
90
- raise
91
-
92
-
93
- # ==============================
94
- # DELETE FUNCTION
95
- # ==============================
96
- def delete_object(bucket: str, key: str) -> None:
97
- """
98
- Deletes an object from an S3 bucket.
99
-
100
- Args:
101
- bucket (str): The name of the S3 bucket.
102
- key (str): The object key to delete.
103
-
104
- Raises:
105
- Exception: Any deletion failure.
106
- """
107
- try:
108
- logger.info(f"🗑️ Deleting object '{key}' from bucket '{bucket}'...")
109
- s3_client.delete_object(Bucket=bucket, Key=key)
110
- logger.info(f"✅ Object '{key}' deleted successfully from '{bucket}'.")
111
-
112
- except ClientError as e:
113
- logger.error(f"❌ AWS Client Error: {e}")
114
- raise
115
-
116
- except BotoCoreError as e:
117
- logger.error(f"❌ BotoCore Error: {e}")
118
- raise
119
-
120
- except Exception as e:
121
- logger.error(f"❌ Unexpected Error: {e}")
122
- raise
123
-
124
-
125
- # ==============================
126
- # MAIN FUNCTION (CLI/DOCKER)
127
- # ==============================
128
- def main():
129
- """
130
- Main entry point for CLI/Docker execution.
131
- """
132
- try:
133
- # ✅ Upload Object
134
- put_object(S3_BUCKET, S3_KEY, LOCAL_FILE_PATH, ACL)
135
-
136
- # ✅ Delete Object (Uncomment if needed)
137
- # delete_object(S3_BUCKET, S3_KEY)
138
-
139
- except Exception as e:
140
- logger.error(f"❌ Error in main: {e}")
141
- raise
142
-
143
-
144
- # ==============================
145
- # AWS LAMBDA HANDLER
146
- # ==============================
147
- def lambda_handler(event, context):
148
- """
149
- AWS Lambda handler for S3 object operations.
150
-
151
- Args:
152
- event (dict): AWS Lambda event payload with 'action', 'bucket', 'key', and 'file_path'.
153
- context: AWS Lambda context object.
154
-
155
- Returns:
156
- dict: Status code and message.
157
- """
158
- try:
159
- action = event.get("action") # 'upload' or 'delete'
160
- bucket = event.get("bucket", S3_BUCKET)
161
- key = event.get("key", S3_KEY)
162
- file_path = event.get("file_path", LOCAL_FILE_PATH)
163
- acl = event.get("acl", ACL)
164
-
165
- if action == "upload":
166
- put_object(bucket, key, file_path, acl)
167
- return {"statusCode": 200, "body": f"File '{key}' uploaded to '{bucket}'."}
168
- elif action == "delete":
169
- delete_object(bucket, key)
170
- return {"statusCode": 200, "body": f"File '{key}' deleted from '{bucket}'."}
171
- else:
172
- raise ValueError("Invalid action. Supported actions: 'upload', 'delete'.")
173
-
174
- except Exception as e:
175
- logger.error(f"❌ Lambda Error: {e}")
176
- return {"statusCode": 500, "body": str(e)}
177
-
178
-
179
- # ==============================
180
- # SCRIPT ENTRY POINT
181
- # ==============================
182
- if __name__ == "__main__":
183
- main()
@@ -1,183 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- AWS Lambda Function for Auto-Tagging EC2 Instances Based on S3 Configuration.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-07
8
- Version: 1.0.0
9
-
10
- Description:
11
- - Fetches tagging configuration from an S3 bucket.
12
- - Applies tags dynamically to an EC2 instance when triggered by AWS CloudTrail events.
13
-
14
- Requirements:
15
- - IAM Role Permissions:
16
- * s3:GetObject
17
- * ec2:CreateTags
18
- * sts:GetCallerIdentity
19
- - Environment Variables:
20
- * S3_BUCKET: Name of the S3 bucket storing tags.json
21
- * S3_OBJECT_KEY: Key for the tags.json file
22
- """
23
-
24
- import json
25
- import os
26
- import re
27
- from typing import Dict, List
28
-
29
- import boto3
30
- from botocore.exceptions import BotoCoreError, ClientError
31
-
32
- from runbooks.utils.logger import configure_logger
33
-
34
- ## ✅ Configure Logger
35
- logger = configure_logger(__name__)
36
-
37
- ## ✅ Initialize AWS Clients
38
- s3 = boto3.client("s3")
39
- ec2 = boto3.client("ec2")
40
-
41
- # ==============================
42
- # CONFIGURATIONS: dafault S3 bucket and object key
43
- # ==============================
44
- BUCKET_NAME = os.getenv("S3_BUCKET", "os-auto-tagging") ## Default S3 bucket name
45
- OBJECT_KEY = os.getenv("S3_OBJECT_KEY", "tags.json") ## Default S3 object key
46
- LOCAL_FILE_PATH = "/tmp/tags.json" ## Local Temp file path
47
-
48
- # ==============================
49
- # VALIDATION CONFIGURATIONS
50
- # ==============================
51
- REQUIRED_TAGS = [
52
- "Account Name",
53
- "Functional Area",
54
- "WBS Code",
55
- "Business Unit",
56
- "Managed by",
57
- "CostGroup",
58
- "TechOwner",
59
- ]
60
-
61
- TAG_VALUE_REGEX = r"^[a-zA-Z0-9\s\-_@]+$" ## Allowed characters for tag values
62
-
63
-
64
- def validate_tags(tags: List[Dict[str, str]]) -> None:
65
- """
66
- Validates that all required tags are present in the tag key-value list.
67
-
68
- Args:
69
- tags (List[Dict[str, str]]): List of tags to validate.
70
-
71
- Raises:
72
- ValueError: If any required tag is missing.
73
- """
74
- tag_keys = {tag["Key"] for tag in tags} ## Extract tag keys
75
- missing_tags = [tag for tag in REQUIRED_TAGS if tag not in tag_keys]
76
-
77
- if missing_tags:
78
- raise ValueError(f"Missing required tags: {', '.join(missing_tags)}")
79
-
80
- ## Validate tag values
81
- for tag in tags:
82
- if not re.match(TAG_VALUE_REGEX, tag["Value"]):
83
- raise ValueError(f"Invalid value '{tag['Value']}' for tag '{tag['Key']}'.")
84
-
85
- logger.info("All required tags are validated and meet constraints.")
86
-
87
-
88
- # ==============================
89
- # S3 UTILITIES
90
- # ==============================
91
- def download_tags_from_s3(bucket: str, key: str, local_path: str) -> List[Dict[str, str]]:
92
- """
93
- Downloads the tagging configuration file from S3 and parses it.
94
-
95
- Args:
96
- bucket (str): The S3 bucket name.
97
- key (str): The object key in the bucket.
98
- local_path (str): The local path to store the file.
99
-
100
- Returns:
101
- List[Dict[str, str]]: List of tags.
102
- """
103
- try:
104
- ## ✅ Download tags.json File from S3
105
- logger.info(f"Downloading '{key}' from bucket '{bucket}'...")
106
- s3.download_file(bucket, key, local_path)
107
- logger.info(f"File downloaded successfully to {local_path}.")
108
-
109
- ## ✅ Parse the tags.json file
110
- with open(local_path, "r") as file:
111
- ## Load tags as a list of dictionaries
112
- tags = json.load(file)
113
-
114
- validate_tags(tags) ## Validate required tags
115
- return tags
116
-
117
- except FileNotFoundError:
118
- logger.error("Local file not found after download.")
119
- raise
120
-
121
- except ClientError as e:
122
- logger.error(f"S3 Client Error: {e}")
123
- raise
124
-
125
- except Exception as e:
126
- logger.error(f"Unexpected error while downloading tags: {e}")
127
- raise
128
-
129
-
130
- # ==============================
131
- # EC2 UTILITIES
132
- # ==============================
133
- def apply_tags_to_instance(instance_id: str, tags: List[Dict[str, str]]) -> None:
134
- """
135
- Applies tags to the specified EC2 instance.
136
-
137
- Args:
138
- instance_id (str): The ID of the EC2 instance.
139
- tags (List[Dict[str, str]]): Tags to apply.
140
-
141
- Raises:
142
- Exception: Any AWS tagging errors.
143
- """
144
- try:
145
- logger.info(f"Applying tags to EC2 instance: {instance_id}")
146
- ec2.create_tags(Resources=[instance_id], Tags=tags)
147
- logger.info(f"Tags successfully applied to instance {instance_id}: {tags}")
148
-
149
- except ClientError as e:
150
- logger.error(f"EC2 Client Error: {e}")
151
- raise
152
-
153
-
154
- # ==============================
155
- # MAIN HANDLER
156
- # ==============================
157
- def lambda_handler(event, context):
158
- """
159
- AWS Lambda Handler for applying tags to EC2 instances.
160
-
161
- Args:
162
- event (dict): AWS event data.
163
- context: AWS Lambda context.
164
- """
165
- try:
166
- ## ✅ Extract instance ID from the event
167
- instance_id = event["detail"]["responseElements"]["instancesSet"]["items"][0]["instanceId"]
168
- logger.info(f"Processing instance ID: {instance_id}")
169
-
170
- ## ✅ Download and Parse Tags
171
- tags = download_tags_from_s3(BUCKET_NAME, OBJECT_KEY, LOCAL_FILE_PATH)
172
-
173
- ## ✅ Apply Tags to Instance
174
- apply_tags_to_instance(instance_id, tags)
175
-
176
- ## ✅ Success Response
177
- return {
178
- "statusCode": 200,
179
- "body": json.dumps(f"Tags successfully applied to instance {instance_id}"),
180
- }
181
- except Exception as e:
182
- logger.error(f"Error during tagging process: {e}")
183
- return {"statusCode": 500, "body": json.dumps(f"Error: {str(e)}")}