Tracking versions in Go

Some of my services were written in Go, and we wanted a way to track which version was used, so we can track and solve issues. Or even see when the application was deployed or from which source was built. The code is on git, so adding a git commit hash to the mix will make our problem easier. Luckily go provides us with a simple way of doing this.

Lets look at some of the stuff I keep track of so I can easily see which version I’m using. I usually call this file version.go and drop it in the root of the package.

package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
)

var version = flag.Bool("version", false, "Print the version and exit")

var BuildVersion string
var BuildHash string
var BuildDate string
var BuildClean string
var Name = "application-name"

func init() {
	flag.Parse()
	if *version {
		fmt.Printf("Name: %s\n", Name);
		fmt.Printf("Version: %s\n", BuildVersion);
		fmt.Printf("Git Commit Hash: %s\n", BuildHash);
		fmt.Printf("Build Date: %s\n", BuildDate);
		fmt.Printf("Built from clean source tree: %s\n", BuildClean)
		fmt.Printf("OS: %s\n", runtime.GOOS)
		fmt.Printf("Architecture: %s\n", runtime.GOARCH)
		os.Exit(1)
	}
}

Lets also create a file called VERSION, and we use that file to keep track of what version is our application. My version file contains the following information

$ cat VERSION
0.0.1

To build the application that will show me the version details I would issue the following command, I usually write a Makefile that will generate the command bellow, with all the necessary information.

go build -ldflags "-X 'main.BuildVersion=$(cat VERSION)' -X 'main.BuildHash=$(git log --pretty=format:'%H' -n 1)' -X 'main.BuildDate=$(date -u)' -X 'main.BuildClean=no'" -o app .

After building the application, we can run it and see the following details

$ ./app -version
Name: application-name
Version: 0.0.1
Git Commit Hash: 5f80f2f91251474da7e92ccddbcabecad0fea35d
Build Date: Thu Oct 13 22:16:08 UTC 2016
Built from clean source tree: no
OS: linux
Architecture: amd64

Usually all our errors are logged to an elasticsearch cluster, and we can easily track and check if there are any errors and from which services the errors occurred.

And here is the Makefile that I use to automate this

.DEFAULT_GOAL := build

BIN_DIR=bin
BIN_FILE=application-name
VERSION_FILE=VERSION
GO_BIN=$(shell which go)

HAS_GO_BIN := $(shell command -v go 2> /dev/null)

GIT_STATUS=$(shell git status --porcelain)
BUILD_VERSION:=$(shell git log --pretty=format:'%h' -n 1)
BUILD_DATE:=$(shell date -u)

ifneq ($(wildcard $(VERSION_FILE)),)
	VERSION:=$(shell cat $(VERSION_FILE))
else
	VERSION:=
endif

ifeq ($(GIT_STATUS),)
  BUILD_CLEAN=yes
else
  BUILD_CLEAN=no
endif

.PHONY: check-env
check-env:
ifndef GOPATH
	$(error GOPATH is undefined, you need to configure your system ...)
endif
ifndef GOROOT
	$(error GOROOT is undefined, you need to configure your system ...)
endif
ifndef HAS_GO_BIN
	$(error go command couldn't be found in PATH)
endif

bin:
	mkdir ${BIN_DIR}

.PHONY: build
build: ${BIN_DIR} check-env
	${GO_BIN} build -ldflags "-X 'main.BuildVersion=${VERSION}' -X 'main.BuildHash=${BUILD_VERSION}' -X 'main.BuildDate=${BUILD_DATE}' -X 'main.BuildClean=${BUILD_CLEAN}'" -o "${BIN_DIR}/${BIN_FILE}" .

.PHONY: clean
clean:
	rm -fv ${BIN_DIR}/${BIN_FILE}

Let me know in the comments if you use some other ways of versioning your go application.