Custom deployments with Codeship
Deploy to a specific Heroku environment based on committer with a custom script
(tl;dr this is a precursor of a longer post on continuous deployment at YunoJuno, and deals specifically with custom Codeship deployments.)
In addition to hosting our dev, uat (staging) and live environments on Heroku, we now have separate Heroku environments (apps) for each developer on the team. We used to allow developers to push their local branches to a single shared dev environment, so that people could easily review work, but in the end it became too messy - everyone overwriting
everyone else's work. So instead we gave everyone their own - it's a cut-down environment, with limited addons, and costs us approx. $40pcm per developer - and half of that is just because we enforce SSL everywhere.
We have been using Codeship for a few weeks now to run our tests - and have Codeship set up to automatically deploy to Heroku whenever the dev or master branches are pushed (more about that in a later post on continuous deployment), but one thing we couldn't do out of the box was control our feature branches - which we wanted to go to the relevant developer's environment. e.g. If Fred pushes a commit to feature/foo-bar we want that branch pushed to Fred's environment on Heroku, so that everyone's current state of work is readily available for review.
In order to make this work as smoothly as possible we had to put together a custom deployment script that would set the target environment based on the branch and committer. It took a bit of wrangling to get there (and a big thank you to Codeship's Marko Locher for all his assistance), but finally we have a custom script that runs on all "feature" branches (we use Gitflow for naming our branches), and deploys to Heroku based on the committer.
It's available as a Gist which we'll keep updated, and looks like this (as of today):
#!/bin/sh
# This codeship custom script depends on:
# - API_KEY env var from Heroku
# It uses the CI_COMMITTER_NAME value to determine which Heroku
# app to deploy to. Each developer has their own environment.
set -e
export HEROKU_API_KEY="${API_KEY}"
# default feature branch app name on Heroku
STAGING_APP_NAME="flintstone-environment"
# update STAGING_APP_NAME based on the name of the committer
# NB this **must** all be on a single line
if [ "${CI_COMMITTER_NAME}" = "Fred" ]; then STAGING_APP_NAME="freds-environment"; elif [ "${CI_COMMITTER_NAME}" = "Barney" ]; then STAGING_APP_NAME="barneys-environment"; fi
STAGING_APP_URL="${STAGING_APP_NAME}.herokuapp.com"
# Turn on Heroku maintenance mode
heroku maintenance:on --app ${STAGING_APP_NAME}
# Push to remote - force a push in all cases on feature branches
git remote add heroku git@heroku.com:${STAGING_APP_NAME}.git
git push heroku -f ${COMMIT_ID}:refs/heads/master
# run migrations and update static content
heroku_run "python manage.py collectstatic --noinput" ${STAGING_APP_NAME}
heroku_run "python manage.py migrate --noinput" ${STAGING_APP_NAME}
# Turn off Heroku maintenance mode
heroku maintenance:off --app ${STAGING_APP_NAME}
# check if the app is up and running
check_url "${STAGING_APP_URL}"