A Go binary designed to manage AWS EC2 instances through AWS Lambda. This service is intended to be called from a web application hosted on AWS Amplify, enabling users to control EC2 instances via simple button clicks.
- Start Instance: Power on a stopped EC2 instance
- Stop Instance: Power off a running EC2 instance
- Restart Instance: Stop and then start an EC2 instance
- Change Instance Type: Modify the instance type of an EC2 instance (automatically stops the instance if needed)
Web Page (AWS Amplify) → AWS Lambda (ec2_manager) → AWS EC2 API
The application receives JSON requests via Lambda, performs the requested EC2 operation using AWS SDK v2, and returns a JSON response indicating success or failure.
The Lambda function expects a JSON payload with the following structure:
{
"action": "start|stop|restart|change_type",
"instance_id": "i-1234567890abcdef0",
"instance_type": "t3.medium"
}-
action(required): The operation to perform. Valid values:start- Start a stopped instancestop- Stop a running instancerestart- Stop and then start an instancechange_type- Change the instance type
-
instance_id(required): The EC2 instance ID (e.g.,i-1234567890abcdef0) -
instance_type(optional): Required only forchange_typeaction. The new instance type (e.g.,t3.medium,t3.large, etc.)
The Lambda function returns a JSON response:
{
"success": true,
"message": "Instance i-1234567890abcdef0 started successfully",
"error": ""
}success(boolean): Whether the operation succeededmessage(string): Human-readable message describing the resulterror(string): Error details if the operation failed (empty on success)
- Go 1.21+ installed
- AWS credentials configured (for testing with real AWS resources)
makeutility (optional, but recommended)
make buildThis creates a bootstrap binary compiled for Linux AMD64 (Lambda's runtime environment) and packages it into ec2_manager.zip ready for Lambda deployment.
Note: AWS Lambda also supports ARM64 (Graviton2) which offers better price-performance. To build for ARM64, modify the Makefile build target to use GOARCH=arm64 and change the runtime to provided.al2023 in the deployment.
make build-localThis creates an ec2_manager binary for your local platform.
If you prefer not to use Make:
# For Lambda
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap main.go
zip ec2_manager.zip bootstrap
# For local
go build -o ec2_manager main.goRun the test suite:
make testRun tests with coverage:
make test-coverageThis generates coverage.html which you can open in a browser to view detailed coverage information.
make depsmake fmtRequires golangci-lint to be installed:
make lintmake cleanThe repository includes a GitHub Actions workflow that automatically deploys the Lambda function when changes are pushed to the main branch.
Prerequisites:
-
Add the following secrets in your GitHub repository settings (Settings → Secrets and variables → Actions):
AWS_ACCESS_KEY_ID: Your AWS access key IDAWS_SECRET_ACCESS_KEY: Your AWS secret access keyLAMBDA_ROLE_ARN: The ARN of the IAM role for Lambda execution (e.g.,arn:aws:iam::123456789012:role/lambda-ec2-manager-role)
-
Ensure the Lambda execution role has the required EC2 permissions (see "Required IAM Permissions" section below)
How it works:
- The workflow builds the Lambda binary for ARM64 (Graviton2)
- Checks if the Lambda function exists
- If it doesn't exist, creates a new function with proper configuration
- If it exists, updates the function code and configuration
- Automatically triggered on push to
mainor can be manually triggered via workflow_dispatch
Use the provided deployment script for interactive deployment:
export LAMBDA_ROLE_ARN="arn:aws:iam::YOUR_ACCOUNT:role/YOUR_LAMBDA_ROLE"
export AWS_REGION="us-east-1" # optional, defaults to us-east-1
./deploy.shThe script will:
- Check prerequisites (AWS CLI, Go, make)
- Validate AWS credentials
- Build the deployment package
- Create or update the Lambda function as needed
-
Build the deployment package:
make build
-
Create a new Lambda function in the AWS Console or via AWS CLI:
aws lambda create-function \ --function-name ec2-manager \ --runtime provided.al2 \ --role arn:aws:iam::YOUR_ACCOUNT:role/YOUR_LAMBDA_ROLE \ --handler bootstrap \ --timeout 360 \ --zip-file fileb://ec2_manager.zip
Important: Set the Lambda timeout to at least 360 seconds (6 minutes) to accommodate instance state transitions, which use 4-minute waiters internally.
-
Configure the Lambda function with appropriate IAM permissions (see below)
-
Set up an API Gateway or Lambda Function URL to make it accessible from your Amplify web application
-
Configure CORS: If using Lambda Function URL, enable CORS in the function configuration. If using API Gateway, configure CORS settings to allow requests from your Amplify domain. The Lambda function returns appropriate CORS headers in responses.
The Lambda function's execution role needs the following EC2 permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:DescribeInstances",
"ec2:ModifyInstanceAttribute"
],
"Resource": "*"
}
]
}For production, consider restricting the Resource field to specific instance ARNs or using condition keys for additional security.
async function manageInstance(action, instanceId, instanceType = null) {
const payload = {
action: action,
instance_id: instanceId
};
if (instanceType) {
payload.instance_type = instanceType;
}
try {
const response = await fetch('YOUR_LAMBDA_URL', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
const result = await response.json();
if (result.success) {
console.log('Success:', result.message);
} else {
console.error('Error:', result.error);
}
} catch (error) {
console.error('Request failed:', error);
}
}
// Example button handlers
document.getElementById('startBtn').addEventListener('click', () => {
manageInstance('start', 'i-1234567890abcdef0');
});
document.getElementById('stopBtn').addEventListener('click', () => {
manageInstance('stop', 'i-1234567890abcdef0');
});
document.getElementById('restartBtn').addEventListener('click', () => {
manageInstance('restart', 'i-1234567890abcdef0');
});
document.getElementById('changeTypeBtn').addEventListener('click', () => {
manageInstance('change_type', 'i-1234567890abcdef0', 't3.medium');
});- Authentication: Implement proper authentication/authorization in your API Gateway or Lambda authorizer before calling this function
- Instance Access: Consider implementing instance-level access control based on user identity
- Rate Limiting: Implement rate limiting to prevent abuse
- Logging: All operations are logged via CloudWatch Logs for audit purposes
- Least Privilege: Grant only necessary EC2 permissions and consider restricting to specific instances
This codebase is designed to be extensible. Potential future features include:
- Reboot instance
- Terminate instance
- Create instance snapshot
- Attach/detach volumes
- Update security groups
- View instance metrics
- Schedule instance start/stop times
See LICENSE file for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass (
make test) - Submit a pull request