Command Line Deployment with Octopus Deploy

| 5 min. (919 words)

Here at Raygun, we use Octopus Deploy to push our changes out to test and production machines. Octopus is pretty easy to use, but I’m a command line junkie at heart so if I can get something done without touching a GUI I’m happy. After deploying to the test environment for the fourth time in a day, I was reminded of this xkcd comic and decided to script something up instead.

Octopus has a command line tool called Octo that can create releases and deploy them. The documentation is pretty good and the command arguments aren’t too lengthy, so I started off just writing a single line command and re-invoking it whenever I needed it.

octo create-release --project=raygun.app --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY

This deploys the latest build of app.raygun.com to our Office environment. This already cuts down the time I spend deploying by at least a minute or two. Then I needed to deploy both our backend app as well as the front end website. Two commands is too easy to stuff up, so it’s time for a shell script!

Now this might sound crazy, but I do most of my scripting in Bash even though I’m on a Windows machine. I already have Mingw32 installed for Git and it has the standard UNIX tooling, so why not? Bash is a lot more sane than batch scripts, that’s for sure.

octo create-release --project=raygun.app --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY
octo create-release --project=raygun.backend --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY

My first version of the shell script just had the two commands in it. Now every deployment I create keeps the backend and web app up to date. Better. Still more to go though.

What about release notes? There’s a bit of extra information we want in our Octopus release notes, like the branch this build is from, who ordered it, and what changed. I can get most of that information out of source control, so why should I have to type it up every time?

NL=$'\n'
BRANCH=`git symbolic-ref --short HEAD`
USER=`id -u -n`
rm -f /tmp/releasenotes
echo "# $BRANCH $NL* $NL#### $USER" > /tmp/releasenotes
vim +2 /tmp/releasenotes

This is a bit chunkier, so we’ll go through it line by line.

  1. Getting newlines into my release notes was a pain, so I assign the newline character to a variable and use that later.
  2. The git command gets the current branch name. This only works if your working directory is inside your repository of course.
  3. The id command gets the username of the current user.
  4. /tmp/releasenotes is where I store the release notes file so I can modify it. Delete it if it exists.
  5. Recreate the release notes file, putting the branch name in an h1 (octopus uses Markdown), starting a bullet list, and putting the current username in an h4 at the bottom of the file.
  6. Launch vim to edit that file, with the cursor already on the second line.

You might want to edit your release notes using something that isn’t vim – all power to you. Regardless of what you use to edit the release notes, once you have that file you can pass it to Octo with the --releasenotesfile parameter.

octo create-release --project=raygun.app --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY \
  --releasenotesfile=/tmp/releasenotes

Now this script is pretty handy, but what if you need to deploy a build that isn’t the latest? Our branches are all built as part of the same project in Octopus, so if someone else has committed to a branch in the mean time you need to tell Octo which version to deploy.

VERSION=""
while getopts "v:" opt; do
  case $opt in
    v)
      VERSION=$OPTARG
      ;;
  esac
done
shift $((OPTIND-1))
[ "$1" = "--" ] && shift

This beauty parses the arguments sent to you. It will error out if an unsupported argument is passed (anything that isn’t -v in this case), or if the user doesn’t pass a value to -v (that’s why we pass v: to getopts, the colon indicates it requires a value). The shift calls at the end move the parsed parameters out so that $1 will be the first non-parsed argument. There’s an additional check for — because getopts sees that as “stop parsing arguments even if they match”, and we don’t want it to leave it in our argument list if it doesn’t do anything.

Now we can use $VERSION in our create-release command. Overall, we end up with the following script:

VERSION=""
while getopts "v:" opt; do
  case $opt in
    v)
      VERSION=$OPTARG
      ;;
  esac
done
shift $((OPTIND-1))
[ "$1" = "--" ] && shift

branch=`git symbolic-ref --short HEAD`
user=`id -u -n`
rm -f /tmp/releasenotes
echo "# $branch $nl* $nl#### $user" > /tmp/releasenotes
vim +2 /tmp/releasenotes

octo create-release --project=raygun.app --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY \
  --releasenotesfile=/tmp/releasenotes \
  --packageversion=$VERSION

octo create-release --project=raygun.backend --deployto=Office \
  --server=$OUR_SERVER --apiKey=$MY_API_KEY \
  --releasenotesfile=/tmp/releasenotes \
  --packageversion=$VERSION

Pretty neat, and takes a bunch of the repetitive actions out of deploying my code to test. Of course this isn’t my final form the final version of my script – I added a few more parameters for the projects to deploy, echoing the command instead of executing it (for testing), a mode for reusing the last release notes (when nothing material has changed), a flag for setting the environment so I can deploy to our Beta site, and a deploy only flag for promoting a release from Office to Beta. If you want to see the whole thing in all it’s glory, check out this gist. Not the prettiest thing I’ve ever written, but it does the job.