Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions python/autoshutdown-lambda/lambda_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import boto3
import logging
import os

# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

Check failure on line 9 in python/autoshutdown-lambda/lambda_handler.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=Dino-s26_helios-octagon-boiler-plate&issues=AZ5F3AoIhUF1R62vWrSK&open=AZ5F3AoIhUF1R62vWrSK&pullRequest=33
action = event.get("action", "").lower()
if action not in ["start", "stop"]:
return {"statusCode": 400, "body": f"Invalid action: {action}. Use 'start' or 'stop'."}

# Gather data from all supported formats
# 1. 'regions' dictionary format: {"us-east-1": ["i-1"], "us-west-2": ["i-2"]}
region_to_ids = event.get("regions", {})

# 2. Singular/List formats (backward compatibility)
instance_id = event.get("instance_id")
instance_ids = event.get("instance_ids", [])
default_region = event.get("region") or os.environ.get("AWS_REGION", "us-east-1")

# Consolidate backward compatibility inputs into the mapping
if instance_id or instance_ids:
all_ids = set(instance_ids)
if instance_id:
all_ids.add(instance_id)

if default_region not in region_to_ids:
region_to_ids[default_region] = []
region_to_ids[default_region].extend(list(all_ids))

if not region_to_ids:
return {"statusCode": 400, "body": "Missing instance IDs or regions"}

results = []
errors = []

for region, ids in region_to_ids.items():
if not ids:
continue

try:
logger.info(f"Performing action '{action}' on instances in {region}: {ids}")
ec2 = boto3.client('ec2', region_name=region)

Check warning on line 45 in python/autoshutdown-lambda/lambda_handler.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Initialize this AWS client outside the Lambda handler function.

See more on https://sonarcloud.io/project/issues?id=Dino-s26_helios-octagon-boiler-plate&issues=AZ5F3AoIhUF1R62vWrSM&open=AZ5F3AoIhUF1R62vWrSM&pullRequest=33

Check failure on line 45 in python/autoshutdown-lambda/lambda_handler.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Set an explicit timeout for this network call to prevent hanging executions in Lambda functions.

See more on https://sonarcloud.io/project/issues?id=Dino-s26_helios-octagon-boiler-plate&issues=AZ5F3AoIhUF1R62vWrSL&open=AZ5F3AoIhUF1R62vWrSL&pullRequest=33

if action == "start":
ec2.start_instances(InstanceIds=ids)
elif action == "stop":
ec2.stop_instances(InstanceIds=ids)

results.append({
"region": region,
"status": f"Successfully {'started' if action == 'start' else 'stopped'}",
"instance_ids": ids
})
except Exception as e:
err_msg = f"Error in {region}: {str(e)}"
logger.error(err_msg)
errors.append(err_msg)

status_code = 200 if not errors else (207 if results else 500)

Check warning on line 62 in python/autoshutdown-lambda/lambda_handler.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=Dino-s26_helios-octagon-boiler-plate&issues=AZ5F3AoIhUF1R62vWrSN&open=AZ5F3AoIhUF1R62vWrSN&pullRequest=33

return {
"statusCode": status_code,
"body": {
"action": action,
"results": results,
"errors": errors
}
}
98 changes: 98 additions & 0 deletions python/autoshutdown-lambda/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# How to use

### Prerequisites
- AWS Lambda Function
- AWS IAM Role
- AWS EventBridge Schedule Rule

### Create Lambda Function
There are 2 way to create lambda function:

1. Using AWS CLI
```
aws lambda create-function --function-name <your-function-name> --architectures arm64 --handler lambda_function.lambda_handler --timeout 90 --memory-size 128 --ephemeral-storage 512 --runtime python3.14 --logging-config '{"LogFormat": "Text"}' --zip-file <code-in-zip>
```

2. Using AWS Console
1. Go to AWS Lambda
2. Click on Create function
3. Click on Author from scratch
4. Fill in the function name, runtime, and architecture
5. Click on Create function then copy-paste the code to the code editor
6. Click on Deploy
7. Copy the Function ARN for EventBridge Schedule Rule

### Create IAM Role
1. Go to AWS IAM
2. Click on Roles
3. Click on Create role
4. Select AWS service
5. Select Lambda
6. Click on Next
7. Select the policies that you want to attach to the role
```
## Policies
- AWSLambdaBasicExecutionRole
- AmazonEC2FullAccess
```
8. Click on Next
9. Click on Create role

### Create EventBridge Schedule Rule
1. Go to AWS EventBridge
2. Click on Rules
3. Click on Create rule
4. Fill in the rule name and description
5. Select the schedule
6. On Invoke part, there will be payload, you can copy-paste the example below:

## For start action
```
{
//for multi region setup, just add more regions and instance ids
"regions": {
"ap-southeast-1": ["i-12345667890abcdef"],
"ap-southeast-3": ["i-0987654321abcdef"]
},
"action": "start"
}
```

## For stop action
```
{
//for multi region setup, just add more regions and instance ids
"regions": {
"ap-southeast-1": ["i-12345667890abcdef"],
"ap-southeast-3": ["i-0987654321abcdef"]
},
"action": "stop"
}
```

7. Click on Next
8. Select the target
9. Click on Create rule

### Test Scheduler

To test the scheduler, try to adjust the time to 1 minute and then check the CloudWatch logs to see if the scheduler is working properly. After the test is completed, remember to change the time back to the desired time.

### Test Lambda Function
To test lambda function manually
1. Go to AWS Lambda
2. Click on your function
3. Click on Test
4. Fill in the payload (make sure the instance id is correct or use the example below)
## Example Payload
```
{
"regions": {
"ap-southeast-1": ["i-045516eb48d2b3bb6"],
"ap-southeast-3": ["i-030c4658d67fc7b9e","i-07651f8193d6a9f6b","i-0285904670b7934d5"]
},
"action": "stop"
}
```
5. Click on Test
6. Check the result for error
Loading