In this blog post I’m going to show you how to inject variables into your Golang executable at build-time. This is most useful for tagging your binary with a version or Git shasum or digest from version control.
You may also like my new ebook – Everyday Golang which is full of practical examples and tips from open-source Go applications including JSON, HTTP servers, embedding, databases, templates and Goroutines.
Here’s an example where the Docker project’s CLI contains a commit ID of 77b4dce
:
$ docker version Client: Version: 17.06.1-ce-rc1 API version: 1.30 Go version: go1.8.3 Git commit: 77b4dce Built: Fri Jul 14 07:38:15 2017 OS/Arch: darwin/amd64
Why add versioning information?
This is a great way to support your users – you can quickly and easily identify which build they are using and how old it could be.
Capture useful variable(s)
Before you get started, think about what makes sense to be injected at build-time? It could be anything from the hostname of the CI machine to the result of a web-service call or more commonly the last commit ID from your Git log.
Let’s use the last Git commit id.
Create a test Git project with a README
$ mkdir /tmp/git-tester && \\ cd /tmp/git-tester && \\ git init && \\ echo "Let's work with Git" > README && \\ git add . && \\ git commit -m "Initial" && \\ echo "Let's keep working" >> README && \\ git add . && \\ git commit -m "First update"
You’ll now see two commits in your Git log:
$ git log commit 67b05a31758848e1e5237ad5ae1dc11c22d4e71e Author: Alex Ellis <alexellis2@gmail.com> Date: Tue Aug 8 08:37:20 2017 +0100 First update commit 9b906b6d02d803111250a974ed8042c4760cde7f Author: Alex Ellis <alexellis2@gmail.com> Date: Tue Aug 8 08:37:20 2017 +0100 Initial
Here’s how you find the ID of your last commit:
$ git rev-list -1 HEAD 67b05a31758848e1e5237ad5ae1dc11c22d4e71e
Next we can capture that into an environmental variable:
$ export GIT_COMMIT=$(git rev-list -1 HEAD) && \\ echo $GIT_COMMIT 67b05a31758848e1e5237ad5ae1dc11c22d4e71e
Prepare your code
Let’s take hello world:
package main import ( "fmt" ) funcmain() { fmt.Println("Hello world") }
In order to pass a build-time variable we need to create a variable within our main package. We’ll call it var GitCommmit string
.
package main import ( "fmt" ) var GitCommitstringfuncmain() { fmt.Printf("Hello world, version: %s\\n", GitCommit) }
Test it out with go build
:
$ go build && \\ ./git-tester Hello world, version:
The version is empty, but we’re now ready to start injecting a variable.
Override go build
Now we need an additional override to our go build
command to pass information to the linker via the -ldflags
flag.
$ export GIT_COMMIT=$(git rev-list -1 HEAD) && \\ go build -ldflags "-X main.GitCommit=$GIT_COMMIT"
Now we see our application has a hard-coded version which we injected at build-time.
$ ./git-tester Hello world, version: 67b05a31758848e1e5237ad5ae1dc11c22d4e71e
Do it with Docker
Once you have worked out your build-time variables it’s likely you will want to update your Dockerfile.
Write a Dockerfile:
FROM golang:1.7.5 WORKDIR /go/src/github.com/alexellis/git-tester COPY .git . COPY app.go . RUN GIT_COMMIT=$(git rev-list -1 HEAD) && \\ go build -ldflags "-X main.GitCommit=$GIT_COMMIT" CMD ["./git-tester"]
Dockerfile
Run a build and then test it out:
$ docker build -t git-tester . $ docker run git-tester Hello world, version: 67b05a31758848e1e5237ad5ae1dc11c22d4e71e
Now this is just a minimal example to show you how to get started with Docker and Go build-time variables. You can take it further and optimize the size of the Docker image by using Multi-stage builds.
Here’s an example of one of my Dockerfiles which builds a Golang application for a Raspberry Pi, MacOS and 64-bit Linux all in one file using multi-stage builds: