Skip to content

Instantly share code, notes, and snippets.

@sacarino
Created January 27, 2023 04:36
Show Gist options
  • Save sacarino/4891afa97091bc0059d38df1da145273 to your computer and use it in GitHub Desktop.
Save sacarino/4891afa97091bc0059d38df1da145273 to your computer and use it in GitHub Desktop.
Bash script to build a docker image, tag it, push to ECR, and deploy to ECS
#!/bin/bash
# Script to build, tag, auth, and deploy to production
# Call it like `./deploy-to-cluster.sh {profile}`
# AWS ECS task definition that we'll be updating at the end.
# This assumes your service and task are named the same thing!
TASK_DEFINITION_NAME="my-task-name"
# name of the cluster you're deploying to
CLUSTER="my-cluster-name"
# The profile name should be passed in as the first argument
# Profiles are configured via the AWS CLI tool, see
# https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
PROFILE=$1
if [ -z "$PROFILE" ]
then
printf "\nFATAL: Build script must be called with the target AWS profile"
exit 1
fi
FILE=".env.${PROFILE}"
if [[ -f $FILE && $FILE =~ ^. ]]
then
printf "\n$FILE exists, loading values into ENV"
set -a; source $FILE; set +a
else
printf "\nFATAL: No $FILE file found"
exit 1
fi
printf "\nProfile: $PROFILE\n"
# Using the profile name, let's get the AWS region this belongs to
# Regions are configured via the AWS CLI tool, see
# https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
printf "\n================ Getting the region for our profile"
REGION=$(aws configure get region --profile $PROFILE)
if [ -z "$REGION" ]
then
printf "\nWARN: Check ~/.aws/config for a $PROFILE entry"
exit 1
fi
printf "\nRegion: $REGION\n"
# get the account ID for your profile's token
# https://stackoverflow.com/questions/33791069/quick-way-to-get-aws-account-number-from-the-aws-cli-tools
printf "\n================ Retrieving the account id for our profile"
ACCOUNT=$(aws sts get-caller-identity --profile $PROFILE --query Account --output text)
if [ -z "$ACCOUNT" ]
then
printf "\nWARN: Check ~/.aws/credentials for a $PROFILE entry"
exit 1
fi
printf "\nAccount: $ACCOUNT\n"
# Build our docker image
DOCKER_BUILD=$(yarn run docker:build); ec=$?
# Bail if there was an error
# https://stackoverflow.com/questions/26675681/how-to-check-the-exit-status-using-an-if-statement
case $ec in
0) ;;
*) printf "\nFATAL: Docker failed to build the image. $DOCKER_BUILD" && exit 1;
esac
# Now we can auth against the ECR instance in the target region
printf "\n================ Authenticating with our AWS region"
AWS_ECR=$(aws ecr get-login-password --region $REGION --profile $PROFILE | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com); ec=$?
case $ec in
0) printf "\nAWS login: success!\n";;
*) printf "\nFATAL: AWS ECR failed. $AWS_ECR" && exit 1;
esac
# Define a timestamp function for tagging
TIMESTAMP="$(TZ=UTC date +%Y-%m-%dT%H%M.%SZ)"
# Pull the version info out of the package.json
VERSION="v$(cat package.json | jq -r '.version')"
# Combine the two for a unique build hash
BUILD="${VERSION}_${TIMESTAMP}"
# Creating our task definition name
NEW_ECR_NAME="$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/${TASK_DEFINITION_NAME}:${BUILD}"
# Tag our image with this build hash
printf "\n================ Tagging the new image as ${BUILD}"
DOCKER_TAG=$(docker tag ${TASK_DEFINITION_NAME}:latest $NEW_ECR_NAME); ec=$?
case $ec in
0) printf "\nTagging: success!\n";;
*) printf '%s\n' "FATAL: Docker tag failed. $DOCKER_TAG" >> build.error ; exit 1;
esac
# Push this build up to the ECR instance
printf "\n================ Pushing ${BUILD} up to ECR"
DOCKER_PUSH=$(docker push $NEW_ECR_NAME); ec=$?
case $ec in
0) printf "\nPushing: success!\n";;
*) printf '%s\n' "FATAL: Docker push failed. $DOCKER_PUSH" >> build.error ; exit 1;
esac
# Getting the current task definition
printf "\n================ Creating a new task definition in ${VERSION}.json"
CURRENT_TASK_DEFINITION=$(aws ecs describe-task-definition --task-definition $TASK_DEFINITION_NAME --profile $PROFILE --region $REGION)
NEW_CONTAINER_DEFS=$(echo $CURRENT_TASK_DEFINITION | jq '.taskDefinition.containerDefinitions' | jq '.[0].image='\"${NEW_ECR_NAME}\")
CURRENT_REV=$(echo $CURRENT_TASK_DEFINITION | jq '.taskDefinition.revision')
NEW_REV="$TASK_DEFINITION_NAME:$((CURRENT_REV+1))"
if [ -z "$NEW_REV" ]
then
printf "\nFATAL: Could not create a new revision of the task definition"
exit 1
else
printf "\nRevision: $NEW_REV\n"
echo $NEW_CONTAINER_DEFS > ${VERSION}.json
fi
printf "\n================ Discovering how many instance of this should run"
DESIRED_COUNT=$(aws ecs describe-services --cluster $CLUSTER --services ${TASK_DEFINITION_NAME} --region $REGION --profile $PROFILE | jq .services[].desiredCount); ec=$?
if [ ${DESIRED_COUNT} = "0" ]; then
DESIRED_COUNT="1"
fi
case $ec in
0) printf "\nInstances: $DESIRED_COUNT\n";;
*) printf '%s\n' "FATAL: desired count failed. $DESIRED_COUNT" >> build.error ; exit 1;
esac
# Create the new task definition
printf "\n================ Registering the task definition"
NEW_TASK_DEFINITION=$(aws ecs register-task-definition --family ${TASK_DEFINITION_NAME} --region $REGION --container-definitions "$(cat ${VERSION}.json)" --profile $PROFILE); ec=$?
case $ec in
0) printf "\nRegister task: success!\n";;
*) printf '%s\n' "FATAL: Register task failed. $NEW_TASK_DEFINITION" >> build.error ; exit 1;
esac
# Update the service to use the latest task definition
printf "\n================ Updating the service with the new task definition and desired count"
UPDATE_SERVICE=$(aws ecs update-service --cluster $CLUSTER --service $TASK_DEFINITION_NAME --task-definition $NEW_REV --desired-count $DESIRED_COUNT --region $REGION --profile govcloud); ec=$?
case $ec in
0) printf "\nService update: success!\n";;
*) printf '%s\n' "FATAL: ECS update service failed. $UPDATE_SERVICE" >> build.error ; exit 1;
esac
printf "\n================ Cleaning up"
rm ${VERSION}.json; ec=$?
case $ec in
0) printf "\nRemoving ${VERSION}.json: success!\n"; exit 0;
*) printf '%s\n' "FATAL: Unable to remove ${VERSION}.json. Please manually remove it." >> build.error ; exit 1;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment