Skip to content

Instantly share code, notes, and snippets.

@stevenringo
Created September 11, 2017 00:13
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevenringo/2aa8485706677c0a92320f17c1efb3a8 to your computer and use it in GitHub Desktop.
Save stevenringo/2aa8485706677c0a92320f17c1efb3a8 to your computer and use it in GitHub Desktop.
Create/update/delete cloudformation stacks with events tailing
set -o pipefail
_exit_error() {
message=$1
code=$2
echo "$message" >&2
exit $code
}
_exit_ok() {
message=$1
echo "$message"
exit 0
}
get-stack-state() {
local stack_name=$1
local region=$2
local stack_state
stack_state=$(aws cloudformation describe-stacks --region $region \
--stack-name $stack_name \
--query "Stacks[0].StackStatus" \
--output text 2>&1)
retcode="$?"
if <<<"${stack_state}" grep -q "does not exist"; then
_exit_ok DOES_NOT_EXIST
fi
if [ $retcode -ne 0 ]; then
_exit_error "$stack_state" $retcode
fi
_exit_ok "$stack_state"
}
get-stack-action() {
local stack_state=$1
local stack_action
case $stack_state in
*_IN_PROGRESS) # busy
_exit_error "Stack is currently in $stack_state state and cannot be updated" 127
;;
*_FAILED) # errored
_exit_error "Stack is currently in $stack_state state and cannot be updated" 127
;;
ROLLBACK_COMPLETE) # ok
stack_action="delete-then-create"
;;
*_COMPLETE) # ok
stack_action="update"
;;
DOES_NOT_EXIST) # nope
stack_action="create"
;;
*) # unknown
;;
esac
echo "$stack_action"
}
upsert-stack() {
local stack_required_action=$1
local stack_name=$2
local stack_template=$3
local region=$4
local upsert_result
local retcode
echo "Attempting to $stack_required_action stack: $stack_name..." >&2
upsert_result=$(aws cloudformation ${stack_required_action}-stack --region $region \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--stack-name $stack_name \
--template-body file://./cloudformation/$stack_template.yml \
--parameters file://./cloudformation/.tmp/cloudformation_parameters-$stack_template.json \
--query StackId \
--output text 2>&1)
retcode="$?"
if <<<"${upsert_result}" grep -q "No updates are to be performed"; then
_exit_ok "No updates are to be performed"
fi
if [ $retcode -ne 0 ]; then
_exit_error "$upsert_result" $retcode
fi
echo "$upsert_result"
}
tail_stack() {
local stack_name=$1
local region=$2
local current
local final_line
local output
local output_result
local previous
local stack_events
local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local headings="| Time | Resource | Status | Message |"
local markline="+--------------+--------------------------------+--------------------------------+--------------------------------------------------------------+"
local header="$markline\n$headings\n$markline\n"
local colour_reset='\033[0m'
local colour_red='\033[31m'
local colour_green='\033[32m'
local colour_yellow='\033[33m'
local colour_blue='\033[34m'
local colour_magenta='\033[35m'
local colour_cyan='\033[36m'
local colour_white='\033[97m'
while true; do
stack_events=$(aws cloudformation describe-stack-events --region $region \
--stack-name $stack_name \
--output text \
--query "sort_by(StackEvents, &Timestamp)[?Timestamp>='$now'].[
Timestamp,
LogicalResourceId,
ResourceStatus,
ResourceStatusReason,
ResourceType
]" 2>&1)
retcode="$?"
echo "$stack_events" | egrep -q "does not exist" && break
if [ $retcode -ne 0 ]; then
_exit_error "$stack_events" $retcode
fi
if [ -z "$stack_events" ]; then
sleep 1
continue
fi
stack_events_table=$(
while IFS=$'\t' read timestamp resource status message rtype; do
# ResourceType (rtype) not used, included for completeness
printf "| %b%-12s%b | %b%-30s%b | %b%-30s%b | %b%-60s%b |\n" \
"$colour_green" "${timestamp:11:8} UTC" "$colour_reset" \
"$colour_red" "$(echo $resource | awk -v len=30 '{ if(length($0)>len) print "…" substr($0,length($0)-len+2,length($0)); else print;}')" "$colour_reset" \
"$colour_cyan" "$(echo $status | awk -v len=30 '{ if(length($0)>len) print "…" substr($0,length($0)-len+2,length($0)); else print;}')" "$colour_reset" \
"$colour_blue" "$(echo $message | awk -v len=60 '{ if(length($0)>len) print "…" substr($0,length($0)-len+2,length($0)); else print;}')" "$colour_reset"
done <<< "$stack_events"
)
current=$(echo -e "$header$stack_events_table")
if [ -z "$previous" ]; then
echo "$current"
elif [ "$current" != "$previous" ]; then
comm -13 <(echo "$previous") <(echo "$current")
fi
previous="$current"
stack_state=$(aws cloudformation describe-stacks --region $region \
--stack-name $stack_name \
--query "Stacks[0].StackStatus" \
--output text 2>&1)
retcode="$?"
echo "$stack_state" | egrep -q ".*_(COMPLETE|FAILED)$" && break
[ $retcode -ne 0 ] && break
sleep 1
done
echo $markline
}
deploy_stack() {
local application=$1
local environment=$2
local stack_template=$3
local region=$4
local stack_name
local stack_required_action
stack_name=$(echo "$application-$environment-$stack_template" | tr '_' '-')
echo "Checking state of stack: $stack_name..."
stack_state=$(get-stack-state $stack_name $region) || exit $?
stack_action=$(get-stack-action $stack_state) || exit $?
if [[ $stack_action == "delete-then-create" ]]; then
echo "Stack exists in non-updatable state. Deleting..." >&2
drop_stack $application $environment $stack_template $region
stack_action="create"
fi
upsert_result=$(upsert-stack $stack_action $stack_name $stack_template $region) || exit $?
if <<<"${upsert_result}" grep -q "No updates are to be performed"; then
_exit_ok "$upsert_result"
fi
echo "Stack with id $upsert_result is being ${stack_action}d"
tail_stack $stack_name $region
}
delete-stack() {
local stack_name=$1
local region=$2
local delete_result
local retcode
echo "Attempting to delete stack: $stack_name..." >&2
delete_result=$(aws cloudformation delete-stack --stack-name $stack_name --region $region 2>&1)
retcode="$?"
if [ $retcode -ne 0 ]; then
_exit_error "$delete_result" $retcode
fi
# delete_result is empty if there is no error, so no return here
}
drop_stack() {
local environment=$1
local application=$2
local stack_template=$3
local region=$4
local stack_name
local stack_required_action
stack_name=$(echo "$environment-$application-$stack_template" | tr '_' '-')
echo "Checking state of stack: $stack_name..."
stack_state=$(get-stack-state $stack_name $region) || exit $?
if <<<"${stack_state}" egrep -q "DOES_NOT_EXIST"; then
_exit_error "The stack $stack_name does not exist"
fi
delete_result=$(delete-stack $stack_name $region) || exit $?
tail_stack $stack_name $region
}
@stevenringo
Copy link
Author

Call deploy_stack to create/update a stack
Call drop_stack to delete a stack

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