RNG

thoughts on software development and everything else

Gitlab CI for Go projects

2019-06-18

Gitlab has a very configurable CI/CD system. Everything is specified in the .gitlab-ci.yml file in the root of your project.

Getting some CI and automated testing up and running for a Go project is pretty straight forward. The official Gitlab post is comprehensive, but complicated as it involves building a docker image and using Makefiles as an additional level of abstraction.

Let’s see how we would build a very simple Go project. In this example, we only have main package files, rather than building libraries.

├── src
│   ├── alpha.go
│   ├── alpha_test.go
│   └── beta
│       ├── beta.go
│       └── beta_test.go
├── .gitignore
├── .gitlab-ci.yml
└── README.md

Here both alpha.go and beta.go are standalone main packages intended to be run with go run (or have a binary built with go build).

Base image

First, the Gitlab runner will need to grab a docker image with Golang tools installed. We use the official Golang image. The default tags 1.12.6, 1.12, 1, latest will get you an image based on Debian (version 9 aka Stretch at time of writing), but you could try the Alpine Linux tags (suffix alpine) if you want a more lightweight image.

We’ll lock in a version rather than use latest so that future changes to Go don’t break things. The semantic versioning should mean that all 1.x versions of Go will be backwards compatible, but it’s good to be sure.

image: golang:1.12.6

Stages

We only want to run tests, not build a binary or deploy, so we will have just one stage in our CI pipeline.

stages: 
  - test

Dependencies

Now we need to pull in any dependencies that our Go files have. To be able to use go get, we need to put the repository in the $GOPATH, which defaults to /go. Furthermore, our projects need to be under the /src/<git domain>/<namespace>/<project> directory

before_script:
  - mkdir -p /go/src/gitlab.com/flying_kiwi /go/src/_/builds
  - cp -r $CI_PROJECT_DIR /go/src/gitlab.com/flying_kiwi/go-ci-demo
  - ln -s /go/src/gitlab.com/flying_kiwi /go/src/_/builds/flying_kiwi
  - go get -v -d ./...

Tests

To run the tests on all the .go files in your project, you can run go test ./... from the root folder.

unit_tests:
  stage: test
  script:
    - go test -v ./...

Coverage

At this stage all our tests are running. What about test coverage? We can use the -coverprofile flag to get coverage data for our tests.

  script:
    - go test -v ./... -coverprofile .testCoverage.txt

To let Gitlab parse the coverage output of go test, we add the following regular expression under Settings > CI/CD > General pipelines > Test coverage parsing

total:\s+\(statements\)\s+(\d+.\d+\%)

You can now add badges to your README so you can see pipeline status and coverage right from the repository page! Look under Settings > CI/CD > General pipelines and scroll down to find the Pipeline status and Coverage report sections. Grab the markdown snippet, pop it in your README.md, and there you go!

Gitlab pipeline and coverage badges

Caching

The official Gitlab tutorial for running CI/CD with Golang suggests the following:

cache:
  paths:
    - /apt-cache
    - /go/src/github.com
    - /go/src/golang.org
    - /go/src/google.golang.org
    - /go/src/gopkg.in

But that doesn’t work. Why? Gitlab only allows caching of files inside of your repository. So the above paths resolve to e.g. /home/user/flying_kiwi/demo-go-ci/go/src/github.com.

This subdirectory caching works great for Node projects, for example, where node_modules will be a subdirectory of your project. But for Go, all the dependencies are stored on the gopath /go. And obviously /apt-cache is completely out.

Variables

Lastly we’re going to refactor this script with some variables so it’s more easily portable to other projects. We will also use the $GOPATH variable in case the default path for our base image changes from /go/.

The final .gitlab-ci.yml file looks like:

image: golang:1.12.6

variables:
  REPO: gitlab.com
  GROUP: flying_kiwi
  PROJECT: go-ci-demo

stages:
 - test

before_script:
  - mkdir -p $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds
  - cp -r $CI_PROJECT_DIR $GOPATH/src/$REPO/$GROUP/$PROJECT
  - ln -s $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds/$GROUP
  - go get -v -d ./...

unit_tests:
  stage: test
  script:
    - go test -v ./... -coverprofile .testCoverage.txt

You can see this project and grab all the files in it at https://gitlab.com/flying_kiwi/go-ci-demo