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
@@ -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()
|