- Ruby 3.3
- EC2 Instances
- RDS DB Instance
IAM > Policies > Create policy > JSON >
[copy + paste]
> Next > Policy name:start-stop-instance-policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"rds:DescribeDBInstances",
"rds:StartDBInstance",
"rds:StopDBInstance"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
IAM > Roles > Create role > Trusted entity type:
AWS service
> Use case:Lambda
> Next > Role name:start-stop-instance-role
default (not change)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
}
IAM > Roles >
start-stop-instance-role
> Add permissions > Attach policies >start-stop-instance-policy
Lambda > Create function > Function name:
start_instances
Environment variables
BASTION_ID = "i-0a1b2c3d4e5f6g7h8",
DEV_APP_ID = "i-9j8h7g6f5e4d3c2b1a0",
RDS_INSTANCE_ID = "dev-instance",
REGION = "us-east-1",
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/..."
# ruby version: 3.3
require 'aws-sdk-ec2'
require 'aws-sdk-rds'
def lambda_handler(*)
# Environment variables
bastion_id = ENV.fetch('BASTION_ID')
dev_app_id = ENV.fetch('DEV_APP_ID')
rds_instance_id = ENV.fetch('RDS_INSTANCE_ID')
region = ENV.fetch('REGION')
slack_webhook_url = ENV.fetch('SLACK_WEBHOOK_URL')
# stop_function_url = ENV.fetch('STOP_FUNCTION_URL')
# Create AWS Clients
ec2_client = Aws::EC2::Client.new(region:)
rds_client = Aws::RDS::Client.new(region:)
# Check if EC2 instances are in a state available to start
ec2_response = ec2_client.describe_instances(instance_ids: [bastion_id, dev_app_id])
ec2_instances = ec2_response.reservations.flat_map(&:instances)
ec2_instances.each do |instance|
ec2_instance_status = instance.state.name
if ec2_instance_status != 'stopped'
return {
status: 422,
message: "The EC2 instance #{instance.instance_id} is not in a state available to be started.",
state: ec2_instance_status
}
end
end
# Check if RDS instance is in a state available to start
rds_response = rds_client.describe_db_instances(db_instance_identifier: rds_instance_id)
rds_instance = rds_response.db_instances.first
rds_instance_status = rds_instance.db_instance_status
if rds_instance_status != 'stopped'
return {
status: 422,
message: "The RDS instance #{rds_instance_id} is not in a state available to be started.",
state: rds_instance_status
}
end
# Start instances
ec2_client.start_instances(instance_ids: [bastion_id, dev_app_id])
rds_client.start_db_instance(db_instance_identifier: rds_instance_id)
# Create Slack message
message = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: ':coffee: Instances started'
}
},
# {
# type: 'section',
# text: {
# type: 'mrkdwn',
# text: "_Click *<#{stop_function_url}|here>* to stop the instances._"
# }
# },
{
type: 'divider'
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'System available in 20 minutes.'
},
accessory: {
type: 'button',
text: {
type: 'plain_text',
text: ':computer: Access system'
},
url: 'http://dev-app.com'
}
}
]
}
# Send Slack message
uri = URI(slack_webhook_url)
Net::HTTP.post(uri, message.to_json, 'Content-Type' => 'application/json')
# Return success message from Lambda
{
status: 200,
message: 'Instances started successfully.'
}
end
Lambda > Functions >
start_instances
> Add trigger > EventBridge > Create a new rule > Rule name:start_instances_event
# Start DEV instance at 05:30h, from Monday to Friday (GMT-3).
cron(30 8 ? * MON-FRI *)
Lambda > Create function > Function name:
stop_instances
Environment variables
BASTION_ID = "i-0a1b2c3d4e5f6g7h8",
DEV_APP_ID = "i-9j8h7g6f5e4d3c2b1a0",
RDS_INSTANCE_ID = "dev-instance",
REGION = "us-east-1",
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/..."
# ruby version: 3.3
require 'aws-sdk-ec2'
require 'aws-sdk-rds'
def lambda_handler(*)
# Environment variables
bastion_id = ENV.fetch('BASTION_ID')
dev_app_id = ENV.fetch('DEV_APP_ID')
rds_instance_id = ENV.fetch('RDS_INSTANCE_ID')
region = ENV.fetch('REGION')
slack_webhook_url = ENV.fetch('SLACK_WEBHOOK_URL')
# start_function_url = ENV.fetch('START_FUNCTION_URL')
# Create AWS Clients
ec2_client = Aws::EC2::Client.new(region:)
rds_client = Aws::RDS::Client.new(region:)
# Check if EC2 instances are in a state available to stop
ec2_response = ec2_client.describe_instances(instance_ids: [bastion_id, dev_app_id])
ec2_instances = ec2_response.reservations.flat_map(&:instances)
ec2_instances.each do |instance|
ec2_instance_status = instance.state.name
if ec2_instance_status != 'running'
return {
status: 422,
message: "The EC2 instance #{instance.instance_id} is not in a state available to be stopped.",
state: ec2_instance_status
}
end
end
# Check if RDS instance is in a state available to stop
rds_response = rds_client.describe_db_instances(db_instance_identifier: rds_instance_id)
rds_instance = rds_response.db_instances.first
rds_instance_status = rds_instance.db_instance_status
if rds_instance_status != 'available'
return {
status: 422,
message: "The RDS instance #{rds_instance_id} is not in a state available to be stopped.",
state: rds_instance_status
}
end
# Stop instances
ec2_client.stop_instances(instance_ids: [bastion_id, dev_app_id])
rds_client.stop_db_instance(db_instance_identifier: rds_instance_id)
# Create Slack message
message = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: ':crescent_moon: Instances stopped'
}
},
# {
# type: 'section',
# text: {
# type: 'mrkdwn',
# text: "_Click *<#{start_function_url}|here>* to start the instances._"
# }
# }
]
}
# Send Slack message
uri = URI(slack_webhook_url)
Net::HTTP.post(uri, message.to_json, 'Content-Type' => 'application/json')
# Return success message from Lambda
{
status: 200,
message: 'Instances successfully disconnected.'
}
end
Lambda > Functions >
stop_instances
> Add trigger > EventBridge > Create a new rule > Rule name:stop_instances_event
# Stop DEV instance at 20:00h, from Monday to Friday (GMT-3).
cron(00 23 ? * MON-FRI *)
References: