Publishing a Go package to GitHub with CircleCI 2.0
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