One of the issues that many companies face when looking at Golang is what to do with the Code that you find out on the “interwebs”. The basic consensus is to either do:
- leave it alone, use
go get
to simply get the current version - “copy”, or “vendor” the code into a local repository for consistent builds
For any sort of repeatable and consistent build to be available, the second is really the only option. How exactly the public (or vendor specific) code moves from one place into your company’s source code control is something that is very different from company to company. Every company has procedures and tools (you hope) to make this process their version of “correct”.
Build Artifact Information
In the end, the important things are less how exactly you vendor your code, but knowing what version of a code base that is currently in use. Being able to quickly inspect a running system or artifact to see the versions of all the vendor’d pieces is paramount to knowing if anything needs to be updated. In this vein, Golang provides an interesting ability with its linker. The Golang linker can inject a string value into un-initialized string variables. The Golang ld Command Docs say:
-X symbol value
Set the value of an otherwise uninitialized string variable.
The symbol name should be of the form importpath.name,
as displayed in the symbol table printed by "go tool nm".
This snippet, along with the Golang go Command Docs give all the pieces necessary to embedd the current build version into a Golang binary. Using this ability, we can inject information into a Golang build, such as Git SHA, build user, etc. These variables can then be used to inspect the running or built artifact to discern what was built. The method to build official artifacts for deployment would then be modified to be something like:
1SHA=$(git rev-parse HEAD)
2go install -a -ldflags "-X github.com/weingart/vendor.buildSHA ${SHA}" example
The vendor package provides the foloowing variables that can be set via the above method:
1buildSHA
2buildTag
3buildUser
4buildTime
5buildComment
Vendor’d Package Information
Unfortunately, expanding the go build
or go install
command line to include
a Git SHA for every vendor’d piece of code gets very unwieldy very quickly. In
order to make this a little easier vendor
is a small package that keeps track
of vendor’d code versions as well as provide a placeholder for the uninitilized
string variables for the local build information. As part of the vendoring of
Golang code, your procedure would add a file to each package that is being
vendor’d. In particular, <package>/vendor.go
or something similar, that
looks something like:
1package vendor_code
2
3import "github.com/weingart/vendor"
4
5func init() {
6 vendor.Add(&vendor.Info{
7 "git-repo": "github.com/vendor/repo",
8 "git-sha": "9e69b5e2e8d4d042f67d9b24f0f69ffb3ab35687",
9 "git-tag": "sometag",
10 })
11}
As packages can have multiple init()
functions, there are no conflicts with
any existing init()
functions within vendor’d packages. The built artifact
can then export this information using the expvar
package, or via the
vendor.Get*()
functions.
1package main
2
3import (
4 _ "expvar"
5 "fmt"
6 "github.com/weingart/vendor"
7 "net/http"
8)
9
10func main() {
11 fmt.Println(vendor.GetInfo())
12 http.ListenAndServe(":8080", nil)
13}
Using the above as an example, the build and vendor information can be found by going to: http://localhost:8080/debug/vars.
The code can be found on Github along with the documentation at GoDoc.