This repo is an abbreviated copy of one used by one of the teams inside Utility Warehouse. It's been built for tight CI integration and developer productivity.
If you're creating a new repo from this template, you'll want to do a search-replace on
github.com/uw-labs/go-mono
and uwlabs
(replacing yourorg
, yourrepo
and yourscm
with your own):
$ find . -type d -name "uwlabs" -exec sh -c 'mv {} $(dirname {})/yourorg' \;
$ find . -type f -exec sed -i 's;github.com/uw-labs/go-mono;yourscm.com/yourorg/yourrepo;g' {} +
$ find . -type f -exec sed -i 's;uwlabs;yourorg;g' {} +
Configure DOCKER_USER
and DOCKER_PASSWORD
in your CircleCI settings for release
builds to work. The registry path to use is configured in the release
CI job.
The CI setup uses Circle CI, partly because of the powerful caching features, and partly
because Circle CI machine users have access to a local docker socket, which
allows us to run integration tests using go test
which work both locally and in CI.
The CI jobs rely on Go build and test caching to be efficient even with a larger number of services.
The same CI setup is used to efficiently test and publish over 130 applications within UW.
- golangci-lint for Go code.
- buf for protobuf linting and breaking change detection.
- gofumports for formatting and imports.
The release
CI job automatically figures out what needs building and publishes a docker
image to the configured registry. It requires the setting of DOCKER_USER
and DOCKER_PASSWORD
in the Circle CI configuration environment variables.
The Dockerfile used to build the images is here.
It can be edited as necessary, just make sure to run make generate
after changing it.
For an example of this, the user-api is published automatically to the local GitHub docker registry whenever it requires rebuilding.
- cmd - Utilities and service applications.
- cmd/calculate-releases Script for calculating applications to publish.
- cmd/deploy Script for building and publishing docker images.
- cmd/user-api Example application with deploy.yml.
- pkg - Shared packages.
- proto - Protobuf definitions & generated code.
- vendor - Vendored third-party dependencies.
A top level Makefile exists to help you perform common actions within the monorepo. Recipes include:
format
- Formats your.go
source codeinstall-generators
- Installs all necessary generators for thegenerate
stepgenerate
- Runs all generators required within the monorepolint-imports
- Runs the import linter.
- Create a new folder in
cmd
for your service E.g.cmd/my-new-service
. - Create a
main.go
- If you want to automatically build a docker container, add a
deploy.yml
file.
Add your own protofiles under proto/uwlabs/. Follow the folder structure laid out in the buf documentation.
calculate-releases is the magic that calculates exactly what applications need to be rebuilt based on file changes to the application directly, or any of its transative dependencies. It is called in CI on every branch push, to calculate which applications to build docker images for.
It relies on go list -json ./...
for dependency information.
deploy is responsible for building and publishing
a docker image to the specified registry. It's usually run on the output of
the calculate-releases
script. It defines a custom format for build configuration
and metadata. This can be extended to include things such as kubernetes
deployment targets, extra application metadata and more. It is currently run
automatically against every branch push in CI.
Use a deploy.yml
together with any main packages that you want to deploy
to configure automatic docker container building and publishing. The deploy.yml
file allows a single configuration parameter:
-
name
Used to configure the name of the docker image pushed to the registry.
When evaluating solutions to two problems, the vendor directory became the primary candidate:
-
How do we keep CI builds as fast as possible?
We first implemented this using module caching, where the first job would download all the modules and cache them for future jobs. It meant a lot of extra boilerplate in the CircleCI configuration files, and it never worked well for the Docker builds. Vendoring means we have all the source code available at all time, and completely removes the need for caching. This sped up builds by roughly 50% in testing.
-
How do we ensure we only release applications that have changed when we perform dependency updates?
This could be done with some custom tooling that can discover file changes between module updates, but this is nontrivial, and we already had a solution that worked with the vendor directory.
The CI pipeline will fail on your pull request if you add a new dependency that is not
within the /vendor
directory. If you've added a new dependency, make sure you run go mod tidy
and go mod vendor
to ensure your dependencies are up-to-date and vendored.