Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@jonleighton
Created December 9, 2016 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonleighton/6044a080dbb7cef035299e07dc7eed9c to your computer and use it in GitHub Desktop.
Save jonleighton/6044a080dbb7cef035299e07dc7eed9c to your computer and use it in GitHub Desktop.
#!/bin/bash
set -e
export AWS_REGION="eu-west-1"
export COMMIT_ID=$1
# Docker Hub account with read-only access to the repository
export DOCKER_HUB_USERNAME="[FILL ME IN]"
export DOCKER_HUB_PASSWORD="[FILL ME IN]"
function check_image_exists {
local baseurl="https://hub.docker.com/v2"
local token=$(
curl --header "Content-Type: application/json" \
--request "POST" \
--data "{\"username\": \"$DOCKER_HUB_USERNAME\", \"password\": \"$DOCKER_HUB_PASSWORD\"}" \
--fail \
--silent \
--show-error \
"$baseurl/users/login/" |
jq --raw-output ".token"
)
local tag_id=$(
curl --header "Authorization: JWT $token" \
--silent \
--fail \
"$baseurl/repositories/loco2/loco2/tags/$COMMIT_ID/" |
jq --raw-output ".id"
)
if [[ "$tag_id" = "" ]]; then
echo "Docker image loco2/loco2:$COMMIT_ID is missing" > /dev/stderr
return 1
else
return 0
fi
}
# While the deploy is ongoing, there will be one "ACTIVE" deployment and one "PRIMARY" one
# When it finishes, there will just be one deployment.
function is_deploy_finished {
local cluster=$1
local service=$2
local deployments_count=$(
aws ecs describe-services --region $AWS_REGION \
--cluster $cluster \
--services $service |
jq '.services[0].deployments | length'
)
if [[ "$deployments_count" = "1" ]]; then
echo "true"
else
echo "false"
fi
}
# Update the task definition to freeze to the specific SHA that we are deploying
function update_task_definition {
local family=$1
local file="$family.json"
aws ecs describe-task-definition --region $AWS_REGION \
--task-definition $family |
jq '.taskDefinition | {family, volumes, containerDefinitions} | .containerDefinitions[].image="loco2/loco2:" + env.COMMIT_ID' > $file
aws ecs register-task-definition --region $AWS_REGION \
--family $family \
--cli-input-json file://$PWD/$file |
jq --raw-output '.taskDefinition.taskDefinitionArn'
rm $file
}
# Update the task definition for a service, and then updates the service to use the new task definition
# revision, triggering a rolling deploy.
function update_service {
local cluster=$1
local service=$2
echo "Updating service $service... "
aws ecs update-service --region $AWS_REGION \
--cluster $cluster \
--service $service \
--task-definition $(update_task_definition $service) \
> /dev/null
echo "Waiting for $service deployment to complete..."
while [ "$(is_deploy_finished $cluster $service)" = "false" ]
do
sleep 1
done
echo "$service deployment complete"
}
function is_stopped_task {
local cluster=$1
local task=$2
local status=$(
aws ecs describe-tasks --region $AWS_REGION \
--cluster $cluster \
--tasks $task |
jq --raw-output '.tasks[0].lastStatus'
)
if [[ "$status" = "STOPPED" ]]; then
echo "true"
else
echo "false"
fi
}
# To prepare the deploy, we update the rake-deploy-prepare task definition to use our deploy SHA,
# and then run it on the worker cluster. This task runs migrations etc.
function prepare_deploy {
local cluster="worker"
local family="rake-deploy-prepare"
echo -n "Running \`rake deploy:prepare\` in new container image... "
local task=$(
aws ecs run-task --region $AWS_REGION \
--cluster $cluster \
--task-definition $(update_task_definition $family) \
--started-by "$(echo $COMMIT_ID | cut -c 1-36)" |
jq --raw-output '.tasks[0].taskArn'
)
while [ "$(is_stopped_task $cluster $task)" = "false" ]
do
sleep 1
done
local exit_code=$(
aws ecs describe-tasks --region $AWS_REGION \
--cluster $cluster \
--tasks $task |
jq --raw-output '.tasks[0].containers[0].exitCode'
)
if [[ "$exit_code" = "0" ]]; then
echo "success"
else
echo "failed"
fi
return $exit_code
}
pids=()
# Runs a child process in the background and adds its pid to the pids array
function run_concurrently {
$@ &
pids+=($!)
}
function wait_for_children {
# We must specify the pid we are waiting for. If we just do "wait" with no
# args, it will wait for all child processes, but will return a successful exit
# status regardless of the exit status of the children.
for pid in ${pids[*]}; do
wait $pid
done
}
# If these fail, we won't continue with the deploy
check_image_exists
prepare_deploy
run_concurrently update_service web web
run_concurrently update_service worker worker-core
run_concurrently update_service worker worker-maintenance-reports
run_concurrently update_service worker worker-maintenance-other
wait_for_children
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment