Publishing a Go package to GitHub with CircleCI 2.0

Masroor Hasan
Jun 2, 2018 · 5 min read

I’ve recently built a CLI in Go and published to GitHub, so wanted to share my end to end process of setting up continuous integration and deployment with CircleCI 2.0.

Set up project

Create a new repo on GitHub for your Go project. I’m using my spotifycli project as an example.

Dependency management

Between godep, govendor and dep, I’ve found dep to be the most intuitive and with awesome documentation. Just pull down the package and initalize for your project:

go get -u https://github.com/golang/dep
dep init

Initializing dep should generate the following:

Gopkg.toml Gopkg.lock vendor/

Whether its a brand new or existing project, vendoring and dependency sync is super simple with dep.

Continuous Integration

CircleCI has ton of documentation for easy integration with GitHub. Once the repo is added to CircleCI, it will start picking up builds as soon as a .circleci/config.yml is added.

Along with the breakdown in the following sections, the full configuration can also be viewed here.

Build environment

version: 2
jobs:
build:
docker:
- image: circleci/golang:1.9
working_directory: /go/src/github.com/masroorhasan/spotifycli
...

The config must always specify the version and a build job. CircleCI 2.0 lets you pick from one of the pre-built language specific (or even your own Dockerfile) docker images. This is great since the pre-built images come pre-installed with a ton of useful tools.

The working_directory sets the workspace directory scope for this job. Go is very specific about Go workspaces and thus it is recommended to be consistent with /go/src/github.com/:username/:reponame.

Build and test steps

...
steps:
- checkout
- run: go get -u github.com/golang/dep/cmd/dep
- run:
name: run build
command: |
dep ensure
go build -v
- run:
name: run tests
command: |
go fmt ./...
go vet ./...
go test -v ./...
...

First step is to checkout which defaults to the working directory. Then, pull down the golang/dep package. Next, we define the build step which runs dep ensure to enforce dependencies are synced. The official dep documentation describes the ensure verb as follows:

This takes care of our set of dependencies before it is safe to run go build. Lastly, we define a step to run our tests. Using some built-in Go tools to format and vet the source code before running tests. The test step can also be extracted to an independant job.

Continuous Deployment

Here we set up another job to create artifacts and publish to GitHub.

Deploy environment

...
deploy:
docker:
- image: circleci/golang:1.9
working_directory: /go/src/github.com/masroorhasan/spotifycli
...

The deploy job uses the same docker image and specifies the same working_directory that will be used to run a deployment.

Utility libraries

...
steps:
- checkout
- run: go get -u github.com/mitchellh/gox
- run: go get -u github.com/tcnksm/ghr
- run: go get -u github.com/stevenmatthewt/semantics
...
  • gox — a cross compilation tool that can be used to create executables for specified OS, architecture, etc.
  • ghr — creates GitHub release and uploads artifacts in parallel.
  • semantics — Manages semantic versioning for CI environments.

Creating artifacts for release

...
- run:
name: cross compile
command: |
gox -os="linux darwin windows" -arch="amd64" -output="dist/spotifycli_{{.OS}}_{{.Arch}}"
cd dist/ && gzip *
...

By default gox will build for all platforms that are compatible with the runtime. Here the artifact generation is limited to Linux, Darwin and Windows 64 bit architecture.

gzip is used to archive the executables in the output dist/ directory. The golang docker image comes with various archiving tools like tar, gzip, bzip2, zip, etc — so use what best works for you.

Pushing the release

...
- add_ssh_keys
- run:
name: create release
command: |
tag=$(semantics --output-tag)
if [ "$tag" ]; then
ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace $tag dist/
else
echo "The commit message(s) did not indicate a major/minor/patch version."
fi
...

Creating a release involves taking the artifacts from previous step and pushing them up to a GitHub tag. ghr publishes the artifacts to the latest tag in the repo. For permissions to create releases, make a personal access token on GitHub and use that to define the $GITHUB_TOKEN environment variable on CircleCI.

semantics is an awesome utility to automate semantic versioning of tags within the CD process. Follow the project documentation to integrate to CircleCI. A commit message with major: , minor: , or patch: will create new git tag to remote and push the release to the newly created tag. Note: commit messages with none of the above keywords will not invoke a new release.

CI/CD workflow

...
workflows:
version: 2
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: master

Finally, we define our build and deploy workflow that puts together sequential steps of the individual jobs. deploy job is dependant on the build job and will only run on the master branch.

Merging a PR to master will run the two jobs sequentially one after the other. Lastly, here is the result on GitHub:

Drop me a comment and let me know how you set up your CircleCI deployments — thanks for reading!

References