Skip to content

Instantly share code, notes, and snippets.

@lantrix
Last active November 13, 2020 17:57
Show Gist options
  • Save lantrix/8269da3e93ed6196541e86c160b82a07 to your computer and use it in GitHub Desktop.
Save lantrix/8269da3e93ed6196541e86c160b82a07 to your computer and use it in GitHub Desktop.
Find yourself creating the same AWS CFN stack a lot during testing? Wasting too much time repeating tags? Put your tags and params in json files and use this bash wrapper script to create the cloudformation stack. Provide your own template file, and modify the stackParams and stackTags to your own needs.
# Create AWS CFN Stack - wrapper for `aws cloudformation create-stack`
#Example Usage:
[ $# -eq 0 ] && { echo -e "\nUsage `basename $0` <stack name> <CFN template file> <JSON parameters file> <JSON tags file>\n\nExample:\n`basename $0` mystackname MyCfn.template stackParams.json stackTags.json\n"; exit 1; }
#Inputs
stackName=${1?param missing - Stack Name}
templateFile=${2?param missing - Template File}
paramsFile=${3?param missing - Json Parameters file}
tagsFile=${4?param missing - Json Tags file}
if [ $# -gt 4 ]; then
echo 1>&2 "$0: too many arguments"
exit 1
fi
#Functions
#-------------------------------------------------------------------------------
# Retrieve the status of a cfn stack
#
# Args:
# $1 The name of the stack
#-------------------------------------------------------------------------------
getStackStatus() {
aws cloudformation describe-stacks \
--stack-name $1 \
--query Stacks[].StackStatus \
--output text
}
#-------------------------------------------------------------------------------
# Waits for a stack to reach a given status. If the stack ever reports any
# status other thatn *_IN_PROGRESS we will return failure status, as all other
# statuses that are not the one we are waiting for are considered terminal
#
# Args:
# $1 Stack name
# $2 The stack status to wait for
#-------------------------------------------------------------------------------
waitForState() {
local status
status=$(getStackStatus $1)
while [[ "$status" != "$2" ]]; do
echo "Waiting for stack $1 to obtain status $2 - Current status: $status"
# If the status is not one of the "_IN_PROGRESS" status' then consider
# this an error
if [[ "$status" != *"_IN_PROGRESS"* ]]; then
exitWithErrorMessage "Unexpected status '$status'"
fi
status=$(getStackStatus $1)
sleep 5
done
echo "Stack $1 obtained $2 status"
}
#-------------------------------------------------------------------------------
# Returns content of JSON file containing params or tags
# Strips out all spaces, newlines etc. for input to aws cli command
# Spaces you want in Values should be encoded. e.g.
# {"Key":"Project","Value":"My\u0020Text"}
#
# Args:
# $1 Path to json file of parameters
#-------------------------------------------------------------------------------
getJsonFileContents() {
json=$(awk -v ORS= -v OFS= '{$1=$1}1' ${1})
echo "$json"
}
#-------------------------------------------------------------------------------
# Exit the program with error status 1.
#
# Args:
# $1 Error message to display when exiting
#-------------------------------------------------------------------------------
exitWithErrorMessage() {
echo "ERROR: $1"
exit 1
}
#-------------------------------------------------------------------------------
# Returns a file URL for the given path
#
# Args:
# $1 Path
#-------------------------------------------------------------------------------
getFileUrl() {
if [[ "$(uname -s)" == "Linux"* ]]; then
# The real thing
echo "file://${1}"
elif [[ "$(uname -s)" == "MINGW"* ]]; then
# Git Bash Hack
echo "file://${1:1:1}:${1:2}"
else
# Mac OS X Hack
echo "file:///${1}"
fi
}
#Create Stack
aws cloudformation create-stack \
--capabilities CAPABILITY_IAM \
--disable-rollback \
--parameters $(getJsonFileContents ${paramsFile}) \
--tags $(getJsonFileContents ${tagsFile}) \
--stack-name ${stackName} \
--template-body $(getFileUrl ${templateFile})
if ! [ "$?" = "0" ]; then
exitWithErrorMessage "Cannot create stack ${stackName}!"
fi
#Wait for completion
waitForState ${stackName} "CREATE_COMPLETE"
[
{"ParameterKey": "AMI", "ParameterValue": "ami-d5b59eb6"},
{"ParameterKey": "VPC", "ParameterValue": "vpc-12345678"},
{"ParameterKey": "AZaSubnet", "ParameterValue": "subnet-a1b2c3d4"},
{"ParameterKey": "AZbSubnet", "ParameterValue": "subnet-1a2b3c4d"},
{"ParameterKey": "ECSKey", "ParameterValue": "MyKey"},
{"ParameterKey": "InstanceType", "ParameterValue": "t2.micro"}
]
[
{"Key":"Project","Value":"My\u0020Project"},
{"Key":"Owner","Value":"[email protected]"}
]
@dannykansas
Copy link

I have a short story for you:

This code was a lifesaver. I wrote a similar script without setting up a git remote for the repo. Deleted the source about a week back and only realized it when I was asked for a demo the other day.

My bash history told the sad tale of an errant tab-complete, and I frantically started to re-write the lost script before taking a moment to look for something similar/adaptable to my use-case.

I'll never make that mistake again, but thought I'd share. OSS saves the day once again, even if it's just a gist.

kudos!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment