Last active
September 12, 2017 02:26
-
-
Save wbyoung/20a39f508c813362bcb7e5a724c6f352 to your computer and use it in GitHub Desktop.
This file contains 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 | |
set -e | |
JQ="jq --raw-output --exit-status" | |
escape-json() { | |
python -c "import sys; import json; sys.stdout.write(json.dumps(sys.stdin.read().strip())[1:-1])" | |
} | |
escape-subst() { | |
# adapted to read from stdin from: https://stackoverflow.com/a/29613573/98069 | |
IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g') | |
printf %s "${REPLY%$'\n'}" | |
} | |
# @env account_id | |
# @env region | |
# @env repository | |
# @env commit_sha | |
# @env service | |
# @returns via stderr | |
container-tag() { | |
echo "$account_id.dkr.ecr.$region.amazonaws.com/$repository:$service-${commit_sha:0:9}" >&2 | |
} | |
# @arg tag | |
# @env region | |
# @env container | |
push-container() { | |
echo 'Logging in...' | |
eval $(aws ecr get-login --no-include-email --region "$region") | |
echo 'Pushing container...' | |
docker tag "$container" "$1" | |
docker push "$1" | |
} | |
# @arg tag | |
# @env account_id | |
# @env region | |
# @env commit_sha | |
# @env task_family | |
# @env task_definition_template | |
# @env config_keys | |
# @env config_value | |
# @returns via stderr | |
register-task() { | |
local escaped_tag=$(echo "$tag" | escape-subst) | |
local definition=$( | |
sed \ | |
-e "s/\${IMAGE_TAG}/$escaped_tag/g" \ | |
-e "s/\${AWS_ACCOUNT_ID}/$account_id/g" \ | |
-e "s/\${COMMIT_SHA}/$commit_sha/g" \ | |
"$task_definition_template" | |
) | |
for i in "${!config_keys[@]}"; do | |
local key="${config_keys[$i]}" | |
local value="${config_values[$i]}" | |
local escaped_value=$(echo "$value" | escape-json | escape-subst) | |
definition=$( | |
echo "$definition" | sed -e "s/\${$key}/$escaped_value/g" | |
) | |
done | |
local active_arns=$( | |
aws ecs list-task-definitions \ | |
--output json \ | |
--region "$region" \ | |
--status ACTIVE \ | |
--family-prefix "$task_family" | | |
$JQ '.taskDefinitionArns[]' | |
) | |
local arn=$( | |
aws ecs register-task-definition \ | |
--output json \ | |
--region "$region" \ | |
--family "$task_family" \ | |
--cli-input-json "$definition" | | |
$JQ '.taskDefinition.taskDefinitionArn' | |
) | |
# deregister all active task definitions | |
for revision in $active_arns; do | |
aws ecs deregister-task-definition \ | |
--region "$region" \ | |
--task-definition $revision > /dev/null | |
done | |
echo "$arn" >&2 | |
echo "Revision: $arn" | |
} | |
# @arg arn | |
# @env region | |
# @env service | |
# @env ?service_update_timeout_exit_status | |
update-service() { | |
if [[ $( | |
aws ecs update-service \ | |
--output json \ | |
--region "$region" \ | |
--cluster default \ | |
--service "$service" \ | |
--task-definition $1 | | |
$JQ '.service.taskDefinition' | |
) != $1 ]]; then | |
echo "Error updating service." | |
return 1 | |
fi | |
for attempt in {1..30}; do | |
if stale=$( | |
aws ecs describe-services \ | |
--output json \ | |
--region "$region" \ | |
--cluster default \ | |
--services "$service" | | |
$JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$1\") | .taskDefinition"); then | |
echo "Waiting for stale deployments:" | |
echo "$stale" | |
sleep 5 | |
else | |
echo "Deployed!" | |
return 0 | |
fi | |
done | |
echo "Service update took too long." | |
return "${service_update_timeout_exit_status-1}" | |
} | |
usage() { | |
if [ $# -gt 1 ]; then | |
echo $@ | |
echo | |
fi | |
echo 'aws-deploy' | |
echo ' --account-id <value>' | |
echo ' --pre-deploy <value>' | |
echo ' --service <value>' | |
echo ' --task-family <value>' | |
echo ' --repository <value>' | |
echo ' --region <value>' | |
echo ' --container <value>' | |
echo ' --task-definition-template <value>' | |
echo ' [--config <key> <value>]' | |
echo | |
echo 'OPTIONS' | |
echo | |
echo ' --account-id (string)' | |
echo ' The AWS account ID' | |
echo | |
echo ' --pre-deploy (string)' | |
echo ' The name of a command to execute after the task definition has ' | |
echo ' been created and before the service is updated/deployed.' | |
echo | |
echo ' --service (string)' | |
echo ' The name of the service' | |
echo | |
echo ' --task-family (string)' | |
echo ' The task family under which to register' | |
echo | |
echo ' --repository (string)' | |
echo ' The name of the repository to push to' | |
echo | |
echo ' --region (string)' | |
echo ' The region' | |
echo | |
echo ' --container (string)' | |
echo ' The container tag name to push' | |
echo | |
echo ' --task-definition-template (path) the task definition template' | |
echo ' The template may include the following placeholders in the style ${VAR}:' | |
echo | |
echo ' AWS_ACCOUNT_ID the account id' | |
echo ' COMMIT_SHA the git commit SHA' | |
echo ' IMAGE_TAG the generated image tag' | |
echo | |
echo ' Additionally, keys may be provided via the config option.' | |
echo | |
echo ' [--config <key> <value>]' | |
echo ' Add a substitution value for the template file, i.e.' | |
echo | |
echo ' aws-deploy --config MY_VAR true' | |
echo | |
echo ' Would replace occurrences of ${MY_VAR} with true in the template. This ' | |
echo ' option can be repeated for multiple config variables.' | |
echo | |
echo ' [--soft-fail]' | |
echo ' Do not fail if service update takes too long. A log message will' | |
echo ' still be generated, but the exit code of the program will not be' | |
echo ' affected' | |
echo | |
} | |
aws-deploy() { | |
local config_keys=() | |
local config_values=() | |
while [ $# -ne 0 ]; do | |
case "$1" in | |
--account-id) | |
shift; local account_id="$1" | |
shift | |
;; | |
--pre-deploy) | |
shift; local pre_deploy="$1" | |
shift | |
;; | |
--service) | |
shift; local service="$1" | |
shift | |
;; | |
--repository) | |
shift; local repository="$1" | |
shift | |
;; | |
--task-family) | |
shift; local task_family="$1" | |
shift | |
;; | |
--region) | |
shift; local region="$1" | |
shift | |
;; | |
--container) | |
shift; local container="$1" | |
shift | |
;; | |
--task-definition-template) | |
shift; local task_definition_template="$1" | |
shift | |
;; | |
-c|--config) | |
shift; config_keys+=("$1") | |
shift; config_values+=("$1") | |
shift | |
;; | |
--soft-fail) | |
shift; local service_update_timeout_exit_status="0" | |
;; | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
*) | |
usage unknown option "$1" | |
exit 1 | |
;; | |
esac | |
done | |
[ -n "$account_id" ] || { usage missing --account-id; exit 1; } | |
[ -n "$service" ] || { usage missing --service; exit 1; } | |
[ -n "$task_family" ] || { usage missing --task-family; exit 1; } | |
[ -n "$repository" ] || { usage missing --repository; exit 1; } | |
[ -n "$region" ] || { usage missing --region; exit 1; } | |
[ -n "$container" ] || { usage missing --container; exit 1; } | |
[ -n "$task_definition_template" ] || { | |
usage missing --task-definition-template; exit 1; | |
} | |
for key in "${config_keys[@]}"; do | |
[ -n "$key" ] || { usage missing --config key; exit 1; } | |
done | |
for value in "${config_values[@]}"; do | |
[ -n "$value" ] || { usage missing --config value; exit 1; } | |
done | |
local commit_sha=$(git rev-parse HEAD) | |
local tag=$(container-tag 3>&2 2>&1 1>&3) | |
push-container "$tag" | |
local arn=$(register-task "$tag" 3>&2 2>&1 1>&3) | |
if [ -n "$pre_deploy" ]; then | |
"$pre_deploy" | |
fi | |
update-service $arn | |
} | |
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then | |
aws-deploy "$@" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage: