Skip to content

Instantly share code, notes, and snippets.

@philoye
Last active August 29, 2015 14:04
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 philoye/37201f54ba8a427551e7 to your computer and use it in GitHub Desktop.
Save philoye/37201f54ba8a427551e7 to your computer and use it in GitHub Desktop.
A simple wrapper around `git push heroku` to gracefully handle when you have migrations to run.
#!/bin/sh -e
# ------------------------------------------------------------------
# Phil Oye
# heroku_deploy
# A simple wrapper around `git push heroku` to gracefully handle
# when you have migrations to run.
#
# ------------------------------------------------------------------
# HOW IT WORKS
#
# If there are no migrations, then push per usual.
#
# If there ARE migrations to run, then:
# 1. Backup database
# * only if remote is named 'production'
# * make sure you have pg_backups enabled.
# 2. Turn on maintenance mode
# 3. Power down workers
# 4. Push
# 5. Run migrations
# 6. Power up workers
# 7. Retart app
# 8. Turn off maintenance mode
#
# If -u <url> is passed, then that URL will be pinged to warm up
# the application.
#
# ------------------------------------------------------------------
# MIT LICENSED
# Copyright (c) 2014 Phil Oye
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------
PROGNAME=${0##*/}
VERSION=0.1
function usage {
echo "usage: heroku_deploy [-hv] [-a appname ] [-r gitremote] [-u pingurl]"
}
function help_message {
echo "
$PROGNAME v$VERSION — A wrapper around git push heroku
$(usage)
-a <app> required Heroku application name
-r <remote> optional Git remote to push to
default is heroku
Suggest renaming remotes to match environment
Only remote named production will backup database
before running migrations
-u <url> optional Ping this URL after deployment to warm up the app
and ensure the site is up
-q optional Suppress log messages from this script
heroku's git push output will remain
-h optional Print this help message
-v optional Print version
"
exit 1
}
if [ $# == 0 ] ; then
help_message
exit 1
fi
function logit () {
if [[ $QUIET != "true" ]]; then
echo $1
fi
}
function exit_with_error () {
logit "$1"
exit 1
}
# set defaults
REMOTE=heroku
while getopts :hqva:r:u: args; do
case $args in
h) help_message ;;
q) QUIET='true' ;;
v) echo "$PROGNAME version $VERSION"; exit ;;
a) APP_NAME="$OPTARG" ;;
r) REMOTE="$OPTARG" ;;
u) PING_URL="$OPTARG" ;;
:) echo "No value for option -$OPTARG."; usage; exit 1 ;;
\?) echo "Unknown option -$OPTARG."; usage; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
function main {
COMMITS_TO_DEPLOY=$(git rev-list --count master...$REMOTE/master)
if [ "$COMMITS_TO_DEPLOY" = "0" ]; then
exit_with_error "Nothing to deploy to $REMOTE."
fi
logit "About to deploy $COMMITS_TO_DEPLOY commits to $REMOTE."
MIGRATIONS=$(git diff HEAD $REMOTE/master --name-only -- db | wc -l)
# migrations require downtime so enter maintenance mode
if test $MIGRATIONS -gt 0; then
logit "There are $MIGRATIONS DB migrations to run."
if [ "$REMOTE" = "production" ]; then
logit "Backing up the $REMOTE database."
heroku pgbackups:capture --expire
else
logit "Skipping database backup on $REMOTE."
fi
logit "Turning on maintenance mode."
PREV_WORKERS=$(heroku ps --app $APP_NAME | grep "^worker." | wc -l | tr -d ' ')
heroku maintenance:on --app $APP_NAME
# Make sure workers are not running during a migration
logit "Turning off $PREV_WORKERS workers."
heroku scale worker=0 --app $APP_NAME
else
logit "No DB migrations to run, so skipping maintenance mode."
fi
logit "Pushing to $REMOTE."
git push $REMOTE
# run database migrations if needed and restart background workers once finished
if test $MIGRATIONS -gt 0; then
logit 'Running migrations...'
heroku run rake db:migrate db:seed --app $APP_NAME
logit "Scaling back up to $PREV_WORKERS workers."
heroku scale worker=$PREV_WORKERS --app $APP_NAME
logit 'Restarting application.'
heroku restart --app $APP_NAME
fi
# echo 'Clearing the cache.'
# rake cache:clear
if test $MIGRATIONS -gt 0; then
logit 'Turning off maintenance mode.'
heroku maintenance:off --app $APP_NAME
fi
logit "Deployed to $REMOTE."
if [ "$REMOTE" = "production" ] && [ "$PING_URL" != '' ]; then
SLEEP_TIME=3
logit "Let's make sure the site is up, and warm up the site in the process."
logit "First, sleep for $SLEEP_TIME seconds..."
sleep $SLEEP_TIME
logit "Ok, let's hit $PING_URL."
CODE=$(curl -sL -w "%{http_code}\\n" "$PING_URL" -o /dev/null)
if [ "$CODE" = "200" ]; then
logit "$PING_URL successfully returned a $CODE."
else
logit "$PING_URL returned a $CODE."
exit 2
fi
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment