Last active
March 7, 2023 11:32
-
-
Save shokoe/e7b4d80b63ea5931ba23bcad1c7684d4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Syntax: ec2_kamino.sh <source instance 'Name' tag> [target new instance 'Name' tag]. | |
# If target name is not given <source name>-clone is used. | |
# Description: Builds a temporary template from a given instance and runs the latest found ami from that template. | |
# New instance settings can be overriden by $overrides. | |
# AMI selection is filtered by $ami_select. | |
# New instance in also tagged with 'clone-source' tag of source instance. | |
# New instance EBS volumes are tagged with the same 'Name' tag of the new instance. | |
# Written by Shoko, Oct2019. | |
# Requires: jq, awscli, standard gnu utils. | |
# Bugs: `aws ec2 get-launch-template-data` does not return 'CreditSpecification>CpuCredits' for t3. | |
# If you need 'standard' add an override. Tested with awscli 1.16.138. | |
help_output="Syntax: ec2_kamino.sh [-h] [-s <source instance name tag>] [-t <target instance name>] [-o <instance start cmd overrides>] [-a <specific ami-id>] [-n <subnet id>]" | |
# opts default | |
############## | |
# template overrides for run-instances | |
overrides="--placement Tenancy=default --instance-type c4.xlarge" | |
#overrides="--placement Tenancy=default" | |
# getopts | |
######### | |
while [ ! -z "$*" ]; do | |
case "$1" in | |
# source instance name | |
-s|--source) shift; ins_name="$1"; target_name="${ins_name}-clone";; | |
# target instance name | |
-t|-target-name) shift; target_name="$1";; | |
# instace start overrides | |
-o|--overrides) shift; overrides="$1";; | |
# source AMI | |
-a|--ami-id) shift; ami="$1";; | |
-n|--subnet) shift; subnet="$1";; | |
-h|--help|*) echo "$help_output"; exit;; | |
esac | |
shift | |
done | |
if [ -z "$ami" ]; then | |
ami_select="Name=tag:origin,Values=$ins_name Name=tag:type,Values=EC2amibackup" | |
else | |
ami_select="Name=image-id,Values=$ami" | |
fi | |
echo "Input Vars: | |
Source Name $ins_name | |
Target Name $target_name | |
Overrides $overrides | |
AMI select $ami_select | |
" | |
[ -z "$ins_name" ] && echo "$help_output" && exit | |
dbg_var(){ echo "${!1}" | sed "s#^# $1: #" >&2; } | |
aws_cmd(){ | |
local S="$1"; local A="$2"; | |
#for w in "$@"; do echo " >$S/$A>>> $w"; done >&2 | |
#echo aws "$@" | sed 's#^# >> #' >&2 | |
local O=$(aws "$@" 2>&1); local E="$?"; | |
# test for valid json since awscli exit codes are shit | |
if [[ $* =~ "--output json" ]] || ([[ ! $* =~ --output ]] && [ `aws configure get output` = "json" ]); then | |
echo "$O" | jq . &>/dev/null; E=$? | |
fi | |
if [ $E -eq 0 ]; then | |
echo "AWS $S $A - OK" >&2 | |
echo "$O" | |
else | |
(tput setaf 1; echo "AWS $S $A - FAIL"; echo "$O" | sed 's#^# ERROR:#'; tput sgr0) >&2 | |
fi | |
return $E | |
} | |
# get instance id by Name tag | |
############################# | |
ins_id=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${ins_name}" "Name=instance-state-name,Values=running" --query 'Reservations[].Instances[0].InstanceId' | head -1` | |
echo "Cloning $ins_name ($ins_id) > $target_name" | |
echo " AMI filter: $ami_select" | |
echo | |
# get latest ami | |
################ | |
# this strange usage of json output with jq flattening at the end (instead of text output) is just for using the json validation in aws_cmd() | |
# cause some developers think that exit codes are not that important and all the rest of the world can just go fuck themselves | |
output_ami=`aws_cmd ec2 describe-images --owners self --output json --filter $ami_select \ | |
--query "Images[].[Name,ImageId,CreationDate,State]" | jq -r '.[] | join(" ")' | \ | |
grep available | sort -k 3r | head -1` | |
[ -z "$output_ami" ] && echo "CRITICAL: AMI not found" && exit 2 | |
read ami_name ami_id ami_date X <<< "$output_ami" | |
ami_age=$((`date +%s`-`date -d "$ami_date" +%s`)) | |
echo " Found AMI '$ami_name' ($ami_id), $(($ami_age/3600)) hours old" | |
echo | |
# create template | |
################# | |
tmpl_source=`aws_cmd ec2 get-launch-template-data --instance-id $ins_id --query 'LaunchTemplateData' --output json` | |
# removals are required to launch template from an ami | |
tmpl_fix=`echo "$tmpl_source" |\ | |
jq 'delpaths([path(.BlockDeviceMappings), path(.NetworkInterfaces[].PrivateIpAddresses), path(.CpuOptions)])'` | |
if [ ! -z "$subnet" ]; then | |
tmpl_fix=`echo "$tmpl_fix" |\ | |
jq --arg subnet "$subnet" '.NetworkInterfaces[0].SubnetId = $subnet' |\ | |
jq 'delpaths([path(.Placement.AvailabilityZone)])'` | |
fi | |
#echo "$tmpl_fix" | sed 's#^# fixed template: #' | cat -n | |
echo | |
#exit | |
output_create=$(aws_cmd ec2 create-launch-template --launch-template-name ${ins_name}-tmp-clone \ | |
--launch-template-data "${tmpl_fix//"/\\"}" --output json --query "LaunchTemplate.CreateTime") | |
[ $? -ne 0 ] && echo "CRITICAL: Create template failed" && exit 2 | |
# run instance | |
############## | |
output_run=$(aws_cmd ec2 run-instances --output json --launch-template LaunchTemplateName=${ins_name}-tmp-clone,Version=1\ | |
--image-id $ami_id \ | |
$overrides \ | |
--tag-specifications \ | |
"ResourceType=instance,Tags=[{Key=Name,Value=$target_name},{Key=clone-source,Value=$ins_name}]" \ | |
"ResourceType=volume,Tags=[{Key=Name,Value=$target_name}]") | |
new_ins_id=`echo "$output_run" | jq -r '.Instances[] | "\(.InstanceId)"'` | |
new_ins_tenancy=`echo "$output_run" | jq -r '.Instances[] | "\(.Placement.Tenancy)"'` | |
echo " New instance ID - $new_ins_id" | |
echo " New instance tenancy - $new_ins_tenancy" | |
echo | |
# tidy up | |
######### | |
output_delete=`aws_cmd ec2 delete-launch-template --launch-template-name ${ins_name}-tmp-clone` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment