Skip to content

Instantly share code, notes, and snippets.

@xntrik
Last active December 10, 2018 04:58
Show Gist options
  • Save xntrik/ca83b2ad8b855194436a01003ebe04b2 to your computer and use it in GitHub Desktop.
Save xntrik/ca83b2ad8b855194436a01003ebe04b2 to your computer and use it in GitHub Desktop.
A simple script to start an AWS EC2 instance and update an associated R53 DNS record. The policy files should setup minimum permissions to allow these actions.
#!/bin/bash
# Dependencies: awscli - see https://aws.amazon.com/cli/
# Some of this is pilfered from
# https://github.com/awslabs/aws-codedeploy-samples
#
# How to use:
# ./aws-manage.sh <instance id> <dns-record> <dns-type> <up|down>
#
# For example:
# ./aws-manage.sh i-7337 xntrik.wtf A up
#
DEBUG=true #print messages
AWS_CLI="aws" #in case you need more CLI options?
WAITER_ATTEMPTS=24 #how many times the wait_for_state tries
WAITER_INTERVAL=5 #and how long we wait each try
TTL=300 #DNS TTL
msg() {
local message=$1
$DEBUG && echo $message 1>&2
}
error_exit() {
local message=$1
echo "[FATAL] $message" 1>&2
exit 1
}
instance_status() {
local instance_id=$1
local state=$($AWS_CLI ec2 describe-instances --instance-ids $instance_id \
--query 'Reservations[0].Instances[0].State.Name' \
--output text)
if [ $? != 0 ]; then
return 1
else
echo $state
return 0
fi
}
wait_for_state() {
local service=$1
local instance_id=$2
local state_name=$3
local instance_state_cmd
if [ "$service" == "ec2" ]; then
instance_state_cmd="instance_status $instance_id"
else
msg "Cannot wait for instance state; unkonwn service type, '$service'"
return 1
fi
msg "Checking $WAITER_ATTEMPTS times, every $WAITER_INTERVAL seconds for instance $instance_id to be in $state_name"
local instance_state=$($instance_state_cmd)
if [ $? != 0 ]; then
msg "Couldn't get initial state"
return 1
fi
local count=1
while [ "$instance_state" != "$state_name" ]; do
if [ $count -ge $WAITER_ATTEMPTS ]; then
local timeout=$(($WAITER_ATTEMPTS * $WAITER_INTERVAL))
msg "Instance failed to reach state, $state_name within $timeout"
return 1
fi
sleep $WAITER_INTERVAL
instance_state=$($instance_state_cmd)
count=$(($count + 1))
msg "Instance is currently in state: $instance_state"
done
return 0
}
stop_instance() {
local instance_id=$1
msg "Checking instance state"
local instance_state=$(instance_status $instance_id)
if [ $? != 0 ]; then
msg "Unable to get instance state"
return 1
fi
if [ "$instance_state" != "running" ]; then
msg "Instance is not running, therefore we can't stop"
return 1
fi
msg "Instance is ready to stop.."
$AWS_CLI ec2 stop-instances --instance-ids $instance_id
if [ $? != 0 ]; then
msg "Failed to stop instance"
return 1
fi
msg "Waiting for instance to be stopped"
wait_for_state "ec2" $instance_id "stopped"
if [ $? != 0 ]; then
msg "Instance didn't appear to stop in time"
return 1
fi
}
start_instance() {
local instance_id=$1
msg "Checking instance state"
local instance_state=$(instance_status $instance_id)
if [ $? != 0 ]; then
msg "Unable to get instance state"
return 1
fi
if [ "$instance_state" != "stopped" ]; then
msg "Instance is not stopped, therefore we can't start"
return 1
fi
msg "Instance is ready to start.."
$AWS_CLI ec2 start-instances --instance-ids $instance_id
if [ $? != 0 ]; then
msg "Failed to start instance"
return 1
fi
msg "Waiting for instance to be started"
wait_for_state "ec2" $instance_id "running"
if [ $? != 0 ]; then
msg "Instance didn't appear to start in time"
return 1
fi
}
get_ipaddress() {
local instance_id=$1
local state=$($AWS_CLI ec2 describe-instances --instance-ids $instance_id \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
if [ $? != 0 ]; then
return 1
else
echo $state
return 0
fi
}
fetch_hosted_zone() {
local dnsname=$1
if [ "${dnsname: -1}" != "." ]; then
dnsname="${dnsname}."
fi
zones=$($AWS_CLI route53 list-hosted-zones --output text \
--query "HostedZones[*].[Id,Name]")
while IFS= read -r line; do
local testdnsname=$(echo $line|cut -f 2 -d " ")
if [ "$testdnsname" == "$dnsname" ]; then
local value=$(echo $line|cut -f 1 -d " ")
value=$(echo $value|cut -f 3 -d "/")
echo $value
return 0
fi
done <<< "$zones"
echo "No $2 DNS Name Found"
return 1
}
fetch_dns_record() {
local hostedzone=$1
local recordtype=$2
records=$($AWS_CLI route53 list-resource-record-sets --hosted-zone-id $1 \
--query "ResourceRecordSets[*].[Type,ResourceRecords[0].Value]" \
--output text)
while IFS= read -r line; do
local testrecordtype=$(echo $line|cut -f 1 -d " ")
if [ "$testrecordtype" == "$recordtype" ]; then
local value=$(echo $line|cut -f 2 -d " ")
echo $value
return 0
fi
done <<< "$records"
echo "No $2 Record Found"
return 1
}
update_ipaddress() {
local ip=$1
local action=$2
local name=$3
local recordtype=$4
local comment=$5
local hostedzone=$6
local temp_file=$(mktemp)
cat >${temp_file} << EOL
{
"Comment": "${comment}",
"Changes": [
{
"Action": "${action}",
"ResourceRecordSet": {
"Name": "${name}",
"Type": "${recordtype}",
"TTL": ${TTL},
"ResourceRecords": [
{
"Value": "${ip}"
}
]
}
}
]
}
EOL
$AWS_CLI route53 change-resource-record-sets --hosted-zone-id $hostedzone \
--change-batch file://$temp_file
rm ${temp_file}
return 0
}
if [ $# -ne 4 ]; then
error_exit "Params: instance-id dns-record dns-type <up|down>"
fi
INSTANCE_ID=$1
DNSNAME=$2
RECORDTYPE=$3
ACTION=$4
if [ $ACTION == "up" ]; then
msg "Starting instance and waiting for it to be in running state"
instance_started=$(start_instance $INSTANCE_ID)
if [ $? != 0 ]; then
error_exit "We couldn't start the instance properly, so we stopped"
fi
msg "Updating the A record"
hz=$(fetch_hosted_zone $DNSNAME)
if [ $? != 0 ]; then
error_exit "We couldn't get the hosted zone for: $DNSNAME"
fi
ip=$(get_ipaddress $INSTANCE_ID)
if [ $? != 0 ]; then
error_exit "We couldn't get the IP address of our instance: $ip"
fi
msg "Instance has the IP: $ip"
updated_ip=$(update_ipaddress $ip "UPSERT" $DNSNAME $RECORDTYPE \
"This is a comment" $hz)
fetch_a=$(fetch_dns_record $hz $RECORDTYPE)
if [ $? != 0 ]; then
error_exit "We didn't update the $RECORDTYPE record properly: $fetch_a"
fi
msg "The $RECORDTYPE record for $DNSNAME is now: $fetch_a"
elif [ $ACTION == "down" ]; then
msg "Stopping instance"
ip=$(get_ipaddress $INSTANCE_ID)
if [ $? != 0 ]; then
error_exit "We couldn't get the IP address of our instance: $ip"
fi
instance_stopped=$(stop_instance $INSTANCE_ID)
if [ $? != 0 ]; then
error_exit "We couldn't stop the instance properly, so we quit.."
fi
msg "Removing the $RECORDTYPE record ($ip) from $DNSNAME"
hz=$(fetch_hosted_zone $DNSNAME)
if [ $? != 0 ]; then
error_exit "We couldn't get the hosted zone for: $DNSNAME"
fi
removed_ip=$(update_ipaddress $ip "DELETE" $DNSNAME $RECORDTYPE \
"Delete" $hz)
fi
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:StopInstances",
"ec2:StartInstances"
],
"Resource": [
"arn:aws:ec2:<region>:<account id>:instance/<instance id>"
]
}
]
}
{
"Version": "2012-10-17",
"Statement":[
{
"Action":[
"route53:ChangeResourceRecordSets",
"route53:GetHostedZone",
"route53:ListResourceRecordSets"
],
"Effect":"Allow",
"Resource":[
"arn:aws:route53:::hostedzone/<hosted zone id>"
]
},
{
"Action":[
"route53:ListHostedZones"
],
"Effect":"Allow",
"Resource":[
"*"
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment