Red Green Repeat Adventures of a Spec Driven Junkie

Incorporating git Version in a Rails App

Ruby on Rails Icon

git logo

Problem

Quick: what is the version of your server?

If you run Ruby on Rails, I know it’s 5.1.6 (the version I use), but what about the version of the added code to the Ruby on Rails framework? Do you: go to your server and ‘poke’ for every feature and fix?

What if you use awesome continuous delivery systems like Kubernetes or Docker swarm and there are multiple instances of your server? How would you know?

How do you know the version you are seeing on the server corresponds to the version in your source control system?

One solution I have used to solve this problem: use the version of the server code to the version your source control system uses. Have a direct link between development and production deploys.

For me, I use git and with Ruby on Rails, that means incorporating the server version in an endpoint.

git version tracking

git tracks the version of code with SHA1. A small change will create a new value of the SHA, even a new branch will as well.

Incorporating this value from development through to production deployment will ensure we know the version the server is running and can track back to source control.

This value automatically generated at the development level. So we just have to incorporate this value in our pipeline.

Let’s get this value into a Ruby on Rails app to start displaying the git version in your Ruby on Rails app.

Requirements

If you would like to follow along in this document, these are the items needed:

  • Ruby - any version, but I am using 2.5.1p57
  • Ruby on Rails - any version, but I am using 5.1.6
  • git - any version, but I am using 2.14.3

Source code for this article is here

New Rails Project

First, let’s create a brand new Ruby on Rails project using Ruby.

$ ruby -v # 2.5.1p47
$ gem install bundler
... # gem install message for bundler
$ echo "ruby '2.5.0'\n source 'https://rubygems.org'\n gem 'rails', '~> 5.1.0'\n" > Gemfile
$ bundle install
... # bundler install messages for rails
$ rails new versioning
... # rails new messages

Do a quick test to make sure things are working:

$ cd rails-git-versioning
$ rails s

In another terminal window:

$ curl localhost:3000
... # lots of output

      <p class="version">
        <strong>Rails version:</strong> 5.1.6<br />
        <strong>Ruby version:</strong> 2.5.1 (x86_64-darwin16)
      </p>
    </section>
  </div>
</body>
</html>

At this point, we confirmed there’s a new Ruby on Rails project running.

New Controller

I will add a new controller that will just respond with the server version.

Let’s create a new controller with command:

$ rails generate controller version

In the app/controllers/version_controller.rb file, insert the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
class VersionController < ApplicationController
  def index
    branch = 'temp_branch'
    sha = 'temp_sha'

    version = {
      branch: branch,
      sha:    sha
    }

    render json: version
  end
end

temp_branch and temp_sha are just placeholders for now. I want to make sure the server responds the way I expect. Since I’m only doing manual testing for this, I want to get other components set up right away.

In the config/routes.rb file, ensure the contenst are:

1
2
3
4
5
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  get 'version', to: 'version#index'
end

Basically, add line 4, but I have the whole file here as reference.

Test Endpoint

Let’s run the server and test out the response from the new /version endpoint:

$ rails s

In another terminal, run the following command:

$ curl http://localhost:3000/version
{ "branch": "test_branch", "sha": "test_sha" }

If this is not working, go back and check the app setup. This is a crucial point as debugging will be the hardest here and the next steps will be automatic.

Please contact me if you need help.

Incorporating git version

With the server returning the version from the /version endpoint, let’s get a real git branch and the sha version in there.

We could just “copy it by hand”, but someone needs to remember to do it every time there’s a code change.

A niftier way would be to directly read the git information from the Rails server.

Modify the branch and sha variables to be:

    branch = `cat .git/HEAD | awk '{ print $2 }'`.strip.split('/')[2..-1].join('/')
    sha = `cat .git/HEAD | awk '{ print $2 }' | xargs -I % sh -c 'cat .git/%'`.strip

Key items used:

.git this is the directory which git keeps the metadata for the project
.git/HEAD this file keeps track of what the working branch is
.git/refs/heads/ this has the current sha of the branch

Instead of having the work done by ruby to get the branch & sha values, I use common UNIX utilities: cat, awk, and xargs. This keeps the code portable to any UNIX system.

Alternative

Another way to get this value, using the git command would be the following commands:

$ git symbolic-ref --short HEAD # branch name
$ git rev-parse HEAD # sha value

This is better because if there’s any changes to the .git directory, this will return the right values.

On any system where the git command is not included, parsing the .git directory will still get the right value.

/version returns git version?

Let’s test this out again, with the server running, execute the following command:

$ curl http://localhost:3000/version
{ "branch": "master", "sha": "66650149ce6a972a6d7a54e240206505f004adb5" }

Create New Branch

Let’s make a new branch and see what the new result is:

$ git checkout -b feature/new_version
$ curl http://localhost:3000/version
{ "branch": "feature/new_version", "sha": "9fd5074bab721b5757305e2944e352434290e96c" }

The server did not even have to restart! Isn’t that nifty?!

Conclusion

Using git as a way to keep track of the version your server is running is easy as adding a new endpoint that grabs the git version.

Having this version will show what version the server is running at any time and is trackable right back to the source code.

I highly recommend this to avoid poking and prodding your server to figure out which version is running, especially when things are on fire.