loading...

Deploy Atomically with Travis & npm

jeffreymfarley profile image Jeff Farley ・4 min read

Deploy Atomically with Travis & npm

I think I am a software developer because I am lazy.

The second or third time I have to perform the same exact task, I find myself saying, “Ugh, can’t I tell the computer how to do it?”Â

So imagine my reaction when our team’s deployment process started looking like this:

  1. git pull
  2. npm run build to create the minified packages
  3. git commit -am "Create Distribution" && git push
  4. Navigate to GitHub
  5. Create a new release

I was thinking steps 1–3 are easy enough to put in a shell script and steps 4–5 are probably scriptable, but is that all? What else needs to be done?

  • The version in package.json was never getting updated and it would be nice to have that in synch with the GitHub release.
  • Can this script be run after the CI build without having to task a human to manually run it?

GitHub and Releases

My first step to full automation was digging into the details of GitHub releases. For starters, tags and releases are not the same thing.

Tags

Tags are part of Git, the underlying version control system. They come in two types:

  • Lightweight – A pointer to an existing commit
  • Annotated – A full object with name, timestamp and checksum hash

However, they do not contain any files or changes to the code in the repository.

Releases

Releases are a GitHub extension of tags. They provide all the functionality of tags while also providing:

  • A zipped archive of the codebase at that point in time
  • Any associated release binaries (like .jar or .dll)

Does this fit our use case?

Not 100%. In our case, the minified files had to exist within the repository as part of a Django plugin in a larger architecture. We couldn’t really take advantage of GitHub releases since their files exist outside the code base.

Also, there seems to be a chicken and egg problem with the package.json version. I don’t know what the version number is until I tag, but I can’t store any code changes with the tag.

npm version to the rescue

The clever folks at node already figured out this problem for me:

npm version [major | minor | patch] -m "message"

When running the command, npm will:

  1. Read the current git tag
  2. Bump the version in package.json according to the type of bump (major, minor or patch)
  3. Run the version script indicated inside of package.json
  4. git add
  5. git commit -m "message"
  6. git tag <new tag number>

Perfect! And if we run npm run build during the version script, the minified files will be packaged along with the updated package.json.

That’s great, how do I get Travis to do that?

We use Travis as our continuous integration server, but this step could be easily ported to CircleCI, AppVeyor or a number of other CI services.

The default use case is for Releases

If you start reading the Travis documentation, it is clear that their main use case is to build assets and push them to GitHub Releases. Since we already determined that isn’t for us, it was time to start experimenting and Googling.

Naïve First Steps

In package.json, I wrote the version script to build the minified assets:

"scripts": {
   ...
   "version": "npm run build && git add ."
   "postversion": "git push && git push --tags"
}

In travis.yml, I had it call npm version before deploying:

before_deploy:
- npm version patch
deploy:
  provider: releases
  skip_cleanup: true
  on:
    branch: master

And then ran it in Travis:

> foo@0.6.2 postversion /home/travis/build/foo/bar
> git push && git push --tags
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use
    git push origin HEAD:<name-of-remote-branch>

Detached Head Mode

After a bit of Googling, it turns out that Travis always runs in detached HEAD mode. It’s sort of a safety measure, but really puts a dent in how this auto deployment is supposed to work.

I may be lazy, but I like a challenge.

Basically, we have to have Travis perform the version steps at the tip of master and then push it to our branch. Wait, how is it going to have the credentials to do that?

API Key

Usually, Travis is performing read-only actions on the repository. When it has to write back to the repository, it needs to know which account to use. We could specify the credentials in the travis.yml file, but then it would be exposed to everyone who views the file online. Doing this properly and safely takes the following (one time) setup:

  1. Create a new token at https://github.com/settings/tokens
  2. Copy the string of characters
  3. Open the settings in Travis
  4. Create a new environment variable GITHUB_API_KEY
  5. Paste the characters in the value and make sure to uncheck “Display value in the build log”

Ok, so the credentials can be exchanged, but we still haven’t fixed the detached head problem.

Writing a Travis Script

A few Google search results (1, 2) showed me that writing a shell script which gets executed by Travis was the solution to the detached head problem.

.travis.yml

.travis/pu.sh

This does everything we set out to do:

  1. Builds the minified files
  2. Increments the version in package.json
  3. Creates a new tag
  4. Pushes it back to the repository
  5. Which triggers another Travis build that…

oh no!

Avoiding the Infinite Loop

Luckily, Travis has a way of saying “don’t run”. Change line 31 to say:

npm version patch -m "chore: release version %s [skip ci]"

Whenever it encounters a commit message that contains [skip ci], Travis will not run.

Next Steps

Wouldn’t it be nice if Travis could also update CHANGELOG.MD too?

Discussion

pic
Editor guide