Created
September 11, 2017 00:13
-
-
Save stevenringo/2aa8485706677c0a92320f17c1efb3a8 to your computer and use it in GitHub Desktop.
Create/update/delete cloudformation stacks with events tailing
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
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Call
deploy_stack
to create/update a stackCall
drop_stack
to delete a stack