blog

Image of fireworks Photo by Snowy Vin on Unsplash

Git Nosetests Heroku Boom! (Lightweight Continuous Integration)

by

Your code isn’t getting any better sitting around undeployed, let’s get it out there.

Continuous Integration is a good thing. Get your commits tested and deployed fast and hassle free and the rewards will pile up in your lap. So what’s the least amount of work it would take to get a simple continuous integration pipeline set up? We want to know when a commit has happened; run the tests; and deploy to a staging server. Let’s say we have a python project, using git for version control, deploying to heroku. Deploying to heroku and running nosetests are both super simple so will this be easy?

TL;DR: Yes.

What we want is:

  • when anyone pushes to a target remote git branch we’ll update a local copy of that remote branch
  • run nosetests on it, and if they are successful
  • push to heroku

In reverse order:

Deploying to heroku is just a git push, you’ve got a git remote set up that points at it, like:

$ git remote -vvv
myStagingHerokuRemote git@heroku.com:supercoolapp-staging.git (fetch)
myStagingHerokuRemote git@heroku.com:supercoolapp-staging.git (push)

and then you just do $ git push myStagingHerokuRemote develop:master (the last arg tells git you want your branch called ‘develop’ to be pushed to the heroku branch ‘master’ which is what heroku uses. If your branch was myAwesomeBranch you’d use that instead of develop, Heroku/git can complain if you’ve been pushing different branches to it so you may need to add a -f, this is not a good idea for day to day usage though)

Nosetests handily returns true if they pass and false otherwise, so triggering only on test success will be something like:

if nosetests
then
    doStuff
else
    echo 'the tests failed'
fi

Triggering when our target git branch is changed is a bit more involved, but not overly so, we have options:

  1. Install a hook on the server if we have access to that
  2. Pretend we only want to trigger when we do something or have every developer install our client side hook (same link as above)
  3. Poll the server to check for changes.

Where 1 seems like it would be the best choice for reals. But for our lightweight example we’ll do 3 which will trigger on other peoples commits but not require us to change the server or get other people to install our stuff.
Seeing as we are already going to want a pristine check out to run our tests on 3 might look something like:

  • git fetch
  • look at the last commit on the remote
  • if different from last time we looked: trigger

all together:

# This script is an example of how you could have lightweight continous integration with git and heroku
# To use you will need to have a separate unused clone of the remote repo you're interested in. And supply that directory as the first argument to this script. You could run it from cron to poll periodically.
# The script then checks to see if the origin/master has changes since last it checked. If it does it does a git pull, runs nosetests from the base directory and if nosetests returns successfully does a git push to your heroku remote.
checkoutDir=$1
hashFilename=latestCommitHash.txt
repoChanged() {
    isChanged=1
    pushd ${checkoutDir}
    git fetch
    latestCommitHash=$(git log origin/master | head -1)
    if [ -a ${hashFilename} ]
    then
        oldCommitHash=$(cat ${hashFilename})
        if [ "${latestCommitHash}" != "${oldCommitHash}" ]
        then
            isChanged=0
        fi
    fi
    echo ${latestCommitHash} > ${hashFilename}
    return ${isChanged}
}
if repoChanged ${checkoutDir}
then
    pushd ${checkoutDir}
    git pull
    if nosetests
    then
        echo 'git push myHerokuRemoteName develop:master'
    else
        echo '!!! Nosetests failed, build broken.'
    fi
    popd
fi

Why on earth would you want to do such a thing? Well, if you’ve got a small project and you’re in control of when it gets deployed to staging, why not make it automatic? Expecting your commits to immediately be available on staging will encourage you to break down your development into smaller functional chunks, which I would say will lead to better development. And if you are in one of those situations where you do need several commits before everything will work again don’t your colleagues deserve to know that in as timely and in your face way as possible? Why not try something like this? what are you waiting for?

+ more