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,213 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- AWS EC2 Instance Launcher.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-07
8
- Version: 1.0.0
9
-
10
- Description:
11
- - Launches EC2 instances in a VPC with specified configurations using AWS Boto3.
12
- - Works with both Python runtime and AWS Lambda.
13
- - Supports environment-based configuration for scalability.
14
-
15
- IAM Role Permissions:
16
- - ec2:RunInstances
17
- - ec2:CreateTags
18
- - ec2:DescribeSecurityGroups
19
- - ec2:DescribeInstances
20
- """
21
-
22
- import json
23
- import os
24
- from typing import Dict, List
25
-
26
- import boto3
27
- from botocore.exceptions import BotoCoreError, ClientError
28
-
29
- from runbooks.utils.logger import configure_logger
30
-
31
- ## ✅ Configure Logger
32
- logger = configure_logger(__name__)
33
-
34
- # ==============================
35
- # CONFIGURATION
36
- # ==============================
37
- DEFAULT_AMI_ID = os.getenv(
38
- "AMI_ID", "ami-03f052ebc3f436d52"
39
- ) ## Default Red Hat Enterprise Linux 9 (HVM), SSD Volume Type
40
- DEFAULT_INSTANCE_TYPE = os.getenv("INSTANCE_TYPE", "t2.micro") ## Default instance type
41
- DEFAULT_MIN_COUNT = int(os.getenv("MIN_COUNT", "1")) ## Min EC2 instances
42
- DEFAULT_MAX_COUNT = int(os.getenv("MAX_COUNT", "1")) ## Max EC2 instances
43
- KEY_NAME = os.getenv("KEY_NAME", "EC2Test") ## SSH Key Pair Name: my-key = EC2Test
44
- ## VPC Security Group IDs
45
- # SECURITY_GROUPS = os.getenv('SECURITY_GROUPS', 'default,vpc_endpoint_security_group').split(',')
46
- SECURITY_GROUP_IDS = os.getenv("SECURITY_GROUP_IDS", "sg-0b0ee7b0b75210174,sg-0b056d8059a91607d").split(",")
47
- SUBNET_ID = os.getenv("SUBNET_ID", "subnet-094569c6e3ccaa04d") ## Required Subnet-ID for VPC-based deployment
48
- TAGS = os.getenv("TAGS", '{"Project":"CloudOps", "Environment":"Dev"}') ## Default tags
49
-
50
- ## ✅ Block Device Mappings Configuration
51
- OS_BlockDeviceMappings = [
52
- {
53
- "DeviceName": "/dev/xvda", ## Root volume device
54
- "Ebs": {
55
- "DeleteOnTermination": True, ## Clean up after instance termination
56
- "VolumeSize": 20, ## Set volume size in GB
57
- "VolumeType": "gp3", ## Modern, faster storage
58
- "Encrypted": True, ## Encrypt the EBS volume
59
- },
60
- },
61
- ]
62
- OS_Monitoring = {"Enabled": False}
63
-
64
-
65
- # ==============================
66
- # AWS CLIENT INITIALIZATION
67
- # ==============================
68
- def get_ec2_client():
69
- """
70
- Initializes AWS EC2 client.
71
- """
72
- return boto3.client("ec2")
73
-
74
-
75
- # ==============================
76
- # EC2 INSTANCE LAUNCH FUNCTION
77
- # ==============================
78
- def launch_ec2_instances(
79
- ec2_client,
80
- ami_id: str,
81
- instance_type: str,
82
- min_count: int,
83
- max_count: int,
84
- key_name: str,
85
- subnet_id: str,
86
- security_group_ids: List[str],
87
- tags: Dict[str, str] = None,
88
- ) -> List[str]:
89
- """
90
- Launches EC2 instances and applies tags.
91
-
92
- Args:
93
- ec2_client: EC2 boto3 client.
94
- ami_id (str): AMI ID for the instance.
95
- instance_type (str): EC2 instance type.
96
- min_count (int): Minimum number of instances.
97
- max_count (int): Maximum number of instances.
98
- key_name (str): SSH key pair name.
99
- subnet_id (str): Subnet ID for launching in a VPC.
100
- security_group_ids (List[str]): Security group IDs for VPC.
101
- tags (Dict[str, str]): Tags to apply to instances.
102
-
103
- Returns:
104
- List[str]: List of launched instance IDs.
105
- """
106
- try:
107
- logger.info("Validates required environment variables for launching EC2 instances.")
108
- if not SUBNET_ID:
109
- raise ValueError("❌ Missing required SUBNET_ID environment variable.")
110
- if not SECURITY_GROUP_IDS or SECURITY_GROUP_IDS == [""]:
111
- raise ValueError("❌ Missing required SECURITY_GROUP_IDS environment variable.")
112
- logger.info("✅ Environment variables validated successfully.")
113
-
114
- logger.info(f"Launching {min_count}-{max_count} instances of type {instance_type} with AMI {ami_id}...")
115
-
116
- ## ✅ Construct parameters
117
- params = {
118
- "BlockDeviceMappings": OS_BlockDeviceMappings,
119
- "ImageId": ami_id,
120
- "InstanceType": instance_type,
121
- "MinCount": min_count,
122
- "MaxCount": max_count,
123
- "Monitoring": OS_Monitoring,
124
- "KeyName": key_name,
125
- "SubnetId": subnet_id, ## VPC subnet
126
- "SecurityGroupIds": security_group_ids, ## VPC Security Group IDs
127
- }
128
-
129
- ## ✅ Launch Instances
130
- response = ec2_client.run_instances(**params)
131
-
132
- ## ✅ Extract Instance IDs
133
- instance_ids = [instance["InstanceId"] for instance in response["Instances"]]
134
- logger.info(f"Launched Instances: {instance_ids}")
135
-
136
- ## ✅ Apply Tags
137
- if tags:
138
- ec2_client.create_tags(
139
- Resources=instance_ids,
140
- Tags=[{"Key": k, "Value": v} for k, v in tags.items()],
141
- )
142
- logger.info(f"Applied tags: {tags}")
143
-
144
- return instance_ids
145
-
146
- except ClientError as e:
147
- logger.error(f"AWS Client Error: {e}")
148
- raise
149
- except BotoCoreError as e:
150
- logger.error(f"BotoCore Error: {e}")
151
- raise
152
- except Exception as e:
153
- logger.error(f"Unexpected error: {e}")
154
- raise
155
-
156
-
157
- # ==============================
158
- # MAIN HANDLER
159
- # ==============================
160
- def lambda_handler(event, context):
161
- """
162
- AWS Lambda Handler for launching EC2 instances.
163
-
164
- Args:
165
- event (dict): AWS event data.
166
- context: AWS Lambda context.
167
- """
168
- try:
169
- ## ✅ Initialize EC2 Client
170
- ec2_client = get_ec2_client()
171
-
172
- ## Parse tags from environment variable
173
- tags = json.loads(TAGS)
174
- ## ✅ Launch EC2 Instances
175
- instance_ids = launch_ec2_instances(
176
- ec2_client=ec2_client,
177
- ami_id=DEFAULT_AMI_ID,
178
- instance_type=DEFAULT_INSTANCE_TYPE,
179
- min_count=DEFAULT_MIN_COUNT,
180
- max_count=DEFAULT_MAX_COUNT,
181
- key_name=KEY_NAME,
182
- subnet_id=SUBNET_ID,
183
- security_group_ids=SECURITY_GROUP_IDS,
184
- tags=tags,
185
- )
186
-
187
- ## ✅ Return Success Response
188
- return {
189
- "statusCode": 200,
190
- "body": json.dumps({"message": "Instances launched", "InstanceIDs": instance_ids}),
191
- }
192
- except Exception as e:
193
- logger.error(f"Lambda Handler Error: {e}")
194
- return {"statusCode": 500, "body": json.dumps({"error": str(e)})}
195
-
196
-
197
- if __name__ == "__main__":
198
- # ✅ CLI Execution for Python Runtime
199
- ec2_client = get_ec2_client()
200
- tags = json.loads(TAGS)
201
-
202
- instance_ids = launch_ec2_instances(
203
- ec2_client=ec2_client,
204
- ami_id=DEFAULT_AMI_ID,
205
- instance_type=DEFAULT_INSTANCE_TYPE,
206
- min_count=DEFAULT_MIN_COUNT,
207
- max_count=DEFAULT_MAX_COUNT,
208
- key_name=KEY_NAME,
209
- subnet_id=SUBNET_ID,
210
- security_group_ids=SECURITY_GROUP_IDS,
211
- tags=tags,
212
- )
213
- print(f"Launched Instances: {instance_ids}")
@@ -1,212 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- EC2 Instance Scheduler for Start/Stop Based on Tags.
5
-
6
- Author: nnthanh101@gmail.com
7
- Date: 2025-01-07
8
- Version: 1.0.0
9
-
10
- Description:
11
- - Start or Stop EC2 instances tagged with "AutoStart" = "True".
12
- - Works in both AWS Lambda and Python environments.
13
-
14
- Requirements:
15
- - IAM Permissions:
16
- * ec2:DescribeInstances
17
- * ec2:StartInstances
18
- * ec2:StopInstances
19
-
20
- Environment Variables (Optional):
21
- - LOG_LEVEL: Logging verbosity (default: INFO)
22
-
23
- Usage (Python CLI):
24
- python ec2_instance_scheduler.py --action=start
25
-
26
- Usage (Lambda):
27
- Trigger event with: {"action": "start"} or {"action": "stop"}
28
- """
29
-
30
- import argparse # # For CLI mode support
31
- import json
32
- import os
33
- import sys
34
- from typing import Dict, List
35
-
36
- import boto3
37
- from botocore.exceptions import (
38
- BotoCoreError,
39
- ClientError,
40
- NoCredentialsError,
41
- PartialCredentialsError,
42
- )
43
-
44
- from runbooks.utils.logger import configure_logger
45
-
46
- ## ✅ Configure Logger
47
- logger = configure_logger(__name__)
48
-
49
-
50
- # ==============================
51
- # AWS CLIENT INITIALIZATION
52
- # ==============================
53
- def get_ec2_client():
54
- """
55
- Initializes the EC2 client.
56
-
57
- Returns:
58
- boto3.client: EC2 client.
59
- """
60
- try:
61
- return boto3.client("ec2")
62
- except (NoCredentialsError, PartialCredentialsError) as e:
63
- logger.error(f"AWS credentials not found or incomplete: {e}")
64
- sys.exit(1)
65
-
66
-
67
- # ==============================
68
- # INSTANCE OPERATIONS
69
- # ==============================
70
- def fetch_instances(client, tag_key: str, tag_value: str) -> List[str]:
71
- """
72
- Fetches instance IDs based on tags.
73
-
74
- Args:
75
- client (boto3.client): EC2 client.
76
- tag_key (str): Tag key to filter instances.
77
- tag_value (str): Tag value to filter instances.
78
-
79
- Returns:
80
- List[str]: List of instance IDs.
81
- """
82
- try:
83
- logger.info("Fetching instances with tag: %s=%s", tag_key, tag_value)
84
-
85
- ## Filter instances based on the tag
86
- response = client.describe_instances(
87
- # Filters=[{'Name': f"tag:AutoStart", 'Values': ['True']}]
88
- Filters=[{"Name": f"tag:{tag_key}", "Values": [tag_value]}]
89
- )
90
-
91
- ## Extract list of Instance IDs
92
- instance_ids = [
93
- instance["InstanceId"] for reservation in response["Reservations"] for instance in reservation["Instances"]
94
- ]
95
-
96
- if not instance_ids:
97
- logger.warning("No matching instances found.")
98
- else:
99
- logger.info("Found instances: %s", instance_ids)
100
-
101
- return instance_ids
102
-
103
- except ClientError as e:
104
- logger.error(f"AWS Client Error: {e}")
105
- raise
106
- except BotoCoreError as e:
107
- logger.error(f"BotoCore Error: {e}")
108
- raise
109
-
110
-
111
- def perform_action(client, instance_ids: List[str], action: str) -> None:
112
- """
113
- Performs the specified action (start/stop) on the instances.
114
-
115
- Args:
116
- client (boto3.client): EC2 client.
117
- instance_ids (List[str]): List of instance IDs.
118
- action (str): The action to perform ("start" or "stop").
119
- """
120
- if not instance_ids:
121
- logger.warning("No instances to process for action: %s", action)
122
- return
123
-
124
- try:
125
- if action == "start":
126
- logger.info("Starting instances: %s", instance_ids)
127
- response = client.start_instances(InstanceIds=instance_ids)
128
- elif action == "stop":
129
- logger.info("Stopping instances: %s", instance_ids)
130
- response = client.stop_instances(InstanceIds=instance_ids)
131
- else:
132
- raise ValueError(f"Invalid action: {action}")
133
-
134
- logger.info("Action '%s' completed successfully.", action)
135
- logger.debug("Response: %s", response)
136
-
137
- except ClientError as e:
138
- logger.error(f"AWS Client Error during '{action}': {e}")
139
- raise
140
- except BotoCoreError as e:
141
- logger.error(f"BotoCore Error during '{action}': {e}")
142
- raise
143
-
144
-
145
- # ==============================
146
- # MAIN HANDLER
147
- # ==============================
148
- def lambda_handler(event, context):
149
- """
150
- AWS Lambda handler for EC2 start/stop scheduler.
151
-
152
- Args:
153
- event (dict): AWS event data.
154
- context: AWS Lambda context.
155
- """
156
- try:
157
- ## ✅ Parse Action from Event
158
- action = event.get("action")
159
- if action not in ["start", "stop"]:
160
- raise ValueError("Invalid action. Supported actions: 'start' or 'stop'.")
161
-
162
- ## ✅ Initialize AWS Client
163
- ec2_client = get_ec2_client()
164
-
165
- # ✅ Fetch Instances and Perform Action
166
- instance_ids = fetch_instances(ec2_client, tag_key="AutoStart", tag_value="True")
167
- perform_action(ec2_client, instance_ids, action)
168
-
169
- return {
170
- "statusCode": 200,
171
- "body": json.dumps(f"Action '{action}' completed successfully."),
172
- }
173
-
174
- except Exception as e:
175
- logger.error(f"Error: {e}")
176
- return {"statusCode": 500, "body": json.dumps(f"Error: {str(e)}")}
177
-
178
-
179
- def main():
180
- """
181
- CLI Entry Point for Python Usage.
182
- """
183
- parser = argparse.ArgumentParser(description="EC2 Scheduler Script")
184
- parser.add_argument(
185
- "--action",
186
- choices=["start", "stop"],
187
- required=True,
188
- help="Action to perform (start/stop).",
189
- )
190
- args = parser.parse_args()
191
-
192
- try:
193
- ## ✅ CLI Execution
194
- action = args.action
195
- ec2_client = get_ec2_client()
196
- instance_ids = fetch_instances(ec2_client, tag_key="AutoStart", tag_value="True")
197
- perform_action(ec2_client, instance_ids, action)
198
- logger.info(f"Action '{action}' completed successfully.")
199
- except Exception as e:
200
- logger.error(f"Failed to execute action: {e}")
201
- sys.exit(1)
202
-
203
-
204
- # ==============================
205
- # SCRIPT ENTRY POINT
206
- # ==============================
207
- if __name__ == "__main__":
208
- # Detect environment
209
- if "AWS_LAMBDA_FUNCTION_NAME" in os.environ:
210
- lambda_handler({}, None) # Placeholder event/context for Lambda testing
211
- else:
212
- main()
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- Terminate EC2 Instances Script.
5
-
6
- This script provides a robust and production-grade solution to terminate EC2 instances.
7
- It supports execution as a standalone Python script, within Docker containers, or as an AWS Lambda function.
8
-
9
- Author: CloudOps DevOps Engineer
10
- Date: 2025-01-08
11
- Version: 1.0.0
12
- """
13
-
14
- import logging
15
- import os
16
- from typing import List
17
-
18
- import boto3
19
- from botocore.exceptions import BotoCoreError, ClientError
20
-
21
- # ==============================
22
- # CONFIGURATION
23
- # ==============================
24
- REGION = os.getenv("AWS_REGION", "us-east-1")
25
- INSTANCE_IDS = os.getenv("INSTANCE_IDS", "").split(",") ## Example: 'i-0158ab7a03bb6a954,i-04a8f37b92b7c1a78'
26
- DRY_RUN = os.getenv("DRY_RUN", "false").lower() == "true"
27
-
28
- # ==============================
29
- # LOGGING CONFIGURATION
30
- # ==============================
31
- logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO)
32
- logger = logging.getLogger(__name__)
33
-
34
- # ==============================
35
- # AWS CLIENT INITIALIZATION
36
- # ==============================
37
- ec2_client = boto3.client("ec2", region_name=REGION)
38
-
39
-
40
- # ==============================
41
- # FUNCTION: Terminate EC2 Instances
42
- # ==============================
43
- def terminate_instances(instance_ids: List[str]) -> List[str]:
44
- """
45
- Terminates specified EC2 instances.
46
-
47
- Args:
48
- instance_ids (List[str]): List of EC2 instance IDs to terminate.
49
-
50
- Returns:
51
- List[str]: List of successfully terminated instance IDs.
52
- """
53
- try:
54
- if not instance_ids or instance_ids == [""]:
55
- logger.error("No instance IDs provided for termination.")
56
- raise ValueError("Instance IDs cannot be empty.")
57
-
58
- logger.info(f"Terminating instances: {', '.join(instance_ids)} in region {REGION}...")
59
- if DRY_RUN:
60
- logger.info("[DRY-RUN] No actual termination performed.")
61
- return []
62
-
63
- # Perform termination
64
- response = ec2_client.terminate_instances(InstanceIds=instance_ids)
65
-
66
- terminated_instances = [instance["InstanceId"] for instance in response["TerminatingInstances"]]
67
- for instance in response["TerminatingInstances"]:
68
- logger.info(f"Instance {instance['InstanceId']} state changed to {instance['CurrentState']['Name']}.")
69
- return terminated_instances
70
-
71
- except ClientError as e:
72
- logger.error(f"AWS Client Error: {e}")
73
- raise
74
-
75
- except BotoCoreError as e:
76
- logger.error(f"BotoCore Error: {e}")
77
- raise
78
-
79
- except Exception as e:
80
- logger.error(f"Unexpected error: {e}")
81
- raise
82
-
83
-
84
- # ==============================
85
- # MAIN FUNCTION (for CLI/Docker)
86
- # ==============================
87
- def main():
88
- """
89
- Main entry point for standalone execution (CLI or Docker).
90
- """
91
- try:
92
- # Ensure instance IDs are provided
93
- if not INSTANCE_IDS or INSTANCE_IDS == [""]:
94
- logger.error("No instance IDs provided. Set INSTANCE_IDS environment variable.")
95
- raise ValueError("Instance IDs are required to terminate EC2 instances.")
96
-
97
- # Terminate instances
98
- terminated_instances = terminate_instances(INSTANCE_IDS)
99
- if terminated_instances:
100
- logger.info(f"Successfully terminated instances: {', '.join(terminated_instances)}")
101
- else:
102
- logger.info("No instances terminated (Dry-Run mode or empty list).")
103
-
104
- except Exception as e:
105
- logger.error(f"Error during instance termination: {e}")
106
- raise
107
-
108
-
109
- # ==============================
110
- # AWS LAMBDA HANDLER
111
- # ==============================
112
- def lambda_handler(event, context):
113
- """
114
- AWS Lambda handler for terminating EC2 instances.
115
-
116
- Args:
117
- event (dict): AWS Lambda event payload. Expected to include instance IDs.
118
- context: AWS Lambda context object.
119
- """
120
- try:
121
- instance_ids = event.get("instance_ids", INSTANCE_IDS)
122
- if not instance_ids or instance_ids == [""]:
123
- logger.error("No instance IDs provided in the Lambda event or environment.")
124
- raise ValueError("Instance IDs are required to terminate EC2 instances.")
125
-
126
- terminated_instances = terminate_instances(instance_ids)
127
- return {
128
- "statusCode": 200,
129
- "body": {
130
- "message": "Instances terminated successfully.",
131
- "terminated_instances": terminated_instances,
132
- },
133
- }
134
- except Exception as e:
135
- logger.error(f"Lambda function failed: {e}")
136
- return {"statusCode": 500, "body": {"message": str(e)}}
137
-
138
-
139
- # ==============================
140
- # SCRIPT ENTRY POINT
141
- # ==============================
142
- if __name__ == "__main__":
143
- main()