Skip to content

Instantly share code, notes, and snippets.

@delijati
Forked from gcchaan/cfn_run.sh
Created May 14, 2020 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save delijati/d639f66d205465f26f9662e668b4dfd1 to your computer and use it in GitHub Desktop.
Save delijati/d639f66d205465f26f9662e668b4dfd1 to your computer and use it in GitHub Desktop.
troposphere runner
#!/bin/bash
# https://gist.github.com/gcchaan/ad8fd83a68467503ec3e6392ebbd519a
set -ef -o pipefail
function message(){
echo 🍣 "$1"
}
function help(){
cat <<- EOF
Usage:
cfn_run.sh [OPTIONS] TEMPLATE_FILE
Options:
-r region
-c leave change-set
-s TODO: silent mode
-h help
Examples:
run:
run.sh -r us-northeast-1 stack.py
EOF
exit 0
}
function set_region(){
readonly master_regions=($(aws ec2 describe-regions | jq -r ".Regions[].RegionName | @text"))
for master_region in "${master_regions[@]}"; do
[[ "$master_region" == "$OPTARG" ]] && region_exists=true
done
if ${region_exists:-false}; then
readonly region_from_option=$OPTARG
message "set region"
echo ${region_from_option}
else
echo "Could not find region '$OPTARG'."
echo "You could choose from below."
echo "${master_regions[@]}"
exit 1
fi
}
while getopts r:c:h opt; do
case "$opt" in
r) set_region $OPTARG;;
c) readonly leaving_change_set=true;;
h) help;;
\?) exit 1;;
esac
done
shift $((--OPTIND))
readonly dir=$(cd "$(dirname "$0")" && pwd)
readonly json_file="$dir"/tmp.json
readonly region=${region_from_option:-ap-northeast-1}
readonly aws_command="aws --region $region"
readonly aws_cfn_command="$aws_command cloudformation"
if [[ -f "$1" ]]; then
template_file=$1
stack_name=$(echo $template_file | sed s/.py// | sed s/_/-/g)
else
echo "Could not find file '$1'"; exit 1
fi
function set_notification(){
message "set notification"
readonly topic_arn=$($aws_command sns list-topics | jq -r '.Topics[].TopicArn')
readonly topic_name=$(echo $topic_arn | sed 's/.*\://')
if [[ $topic_name == "SlackTopic" ]]; then
readonly notification_flag="--notification-arns $topic_arn "
echo "$topic_arn"
else
readonly notification_flag=""
echo "no setting."
fi
}
function remove_local_json(){
message "removing local temporary file"
rm "$1"
}
function display_change_set_url(){
message "change set URL here."
[[ -z $1 ]] && echo "arn could not find." && return
local _change_set_arn=$1
echo "https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/changeset/detail?changeSetId=${_change_set_arn}"
}
function delete_change_set(){
message "delete change set"
[[ -z $1 ]] && echo "arn could not find." && return
local _change_set_arn=$1
if ${leaving_change_set:-false}; then
echo "skipped."
else
$aws_command cloudformation delete-change-set --change-set-name $_change_set_arn
echo "change set is deleted. if you would leave it, use '-c'."
fi
}
function display_stack_url(){
message "Management console URL here."
[[ -z $1 ]] && echo "arn could not find." && return
local _stack_arn=$1
echo "https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stack/detail?stackId=${_stack_arn}"
}
function confirm_prompt(){
echo
message "execute? (Y/n): "
read answer
case $answer in
y|Y|yes)
echo -e "tyeped yes.\n"
return 0
;;
n|N|no)
echo -e "tyeped no.\n"
remove_local_json $json_file
return 1
;;
*)
echo -e "cannot understand $answer.\n"
confirm_prompt
;;
esac
}
function update_stack(){
message "creating change-set"
readonly change_set=$(
$aws_cfn_command create-change-set \
--change-set-name ${stack_name}-$(date "+%Y%m%d%H%M%S") \
--stack-name ${stack_name} \
--template-body file://${json_file} \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM)
echo ${change_set} | jq
readonly stack_arn=$(echo ${change_set} | jq -r '.StackId')
readonly change_set_arn=$(echo ${change_set} | jq -r '.Id')
sleep 1
while true; do
change_set_json=$($aws_cfn_command describe-change-set --change-set-name $change_set_arn)
change_set_status=$(echo $change_set_json | jq '.Status')
case $change_set_status in
'"CREATE_COMPLETE"')
message "describe change-set"
echo ${change_set_json} | jq
display_change_set_url "$change_set_arn"
confirm_prompt
message "update stack"
$aws_cfn_command update-stack \
--stack-name "$stack_name" \
--template-body file://"$json_file" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
$notification_flag
break;;
'"FAILED"')
message "describe change-set"
echo ${change_set_json} | jq
message "Error occured. show 'StatusReason' of change-set here."
echo ${change_set_json} | jq '.StatusReason'
break;;
* )
sleep 1
echo "status still ${change_set_status}..." ;;
esac
done
delete_change_set "$change_set_arn"
display_stack_url "$stack_arn"
}
function create_stack(){
message "creation preview"
cat $json_file | jq
confirm_prompt
message "create stack"
readonly create_stack_result_json=$($aws_cfn_command create-stack \
--stack-name "$stack_name" \
--template-body file://"$json_file" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
$notification_flag)
readonly stack_arn=$(echo $create_stack_result_json | jq -r '.StackId')
echo "executed."
display_stack_url "$stack_arn"
}
message "flake8 linting"
flake8 $template_file
message "converting json"
python $template_file > "$json_file"
message "checking json syntax"
$aws_cfn_command validate-template --template-body file://"$json_file" | jq
message "fetch list"
readonly stacks=($($aws_cfn_command list-stacks \
--query 'StackSummaries[?StackStatus!=`DELETE_COMPLETE`]' \
| jq -r '.[].StackName'))
echo ${stacks[@]}
stack_exists=false
for stack in "${stacks[@]}"; do
[[ "$stack" == "$stack_name" ]] && stack_exists=true
done
set_notification
if "$stack_exists"; then
update_stack
else
create_stack
fi
remove_local_json $json_file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment