Continuous integration with Go and GitHub Actions
In this post we're going to walk through how to use GitHub Actions to create a continuous integration (CI) pipeline that automatically tests, vets and lints your Go code.
For solo projects I usually create a pre-commit Git hook to carry out these kinds of checks, but for team projects or open-source work — where you don't have control over everyone's development environment — using a CI workflow is a great way to flag up potential problems and help catch bugs before they make it into production or a versioned release.
And if you're already using GitHub to host your repository, it's nice and easy to use their built-in functionality to do this without any need for additional third-party tools or services.
To demonstrate how it works, let's run through a step-by-step example.
If you'd like to follow along, please create a new repository and clone it to your local machine. For the purpose of this post I'm going to use the private repository
Then let's scaffold a simple Go application along with a (failing) test like so:
If you run this application it should compile correctly and print
"Hi Alice", but executing
go test . will result in a failure. Similar to this:
Creating a workflow file
The next thing that we want to do is create a workflow file which describes what we want to do in our CI checks, and when we want them to run. By convention this file should be stored in a
.github/workflow directory in the root of your repository and should be in YAML format.
Let's create this directory along with an
audit.yml workflow file.
But for now, let's jump in and update the workflow file so that it looks like this:
Let's quickly step through this and explain what the different parts of the file do.
- First we use the
onkeyword to define when we want the workflow to run. In this case, I've configured the workflow so that it runs when a new commit is made to the
mainbranch, or a pull request is submitted.
- Then we use the
jobskeyword to define a list of the jobs that are to be run. At the moment our workflow only contains one job called
audit, but you can specify multiple jobs if you want and (by default) they will be executed in parallel.
- An independent runner will be spun up for each job. This is essentially a virtual machine that will execute the
stepsfor the job. In the file above we use the
runs-onkeyword to specify that we want the runner to use Ubuntu 20.04 as a base OS, but others operating systems are available. It's also worth noting that the runner has a lot of useful software and tooling pre-installed.
- In the first step for our
auditjob we use the
useskeyword to execute the community action
actions/checkout@v2. This action will checkout our project repository to the runner so that the following steps access the code.
- Then we use the
actions/setup-go@v2action to install Go version 1.17 on the runner.
- Once that's done, in the remaining steps we use the
runkeyword to execute specific commands on the runner. In this case we build our code and then audit it using the standard
go build|vet|testcommands and the additional
Now that's in place, let's commit everything and push the changes to your repository:
Once the push has completed, head to your repository and select the Actions tab. You should see that the CI 'Audit' workflow is running, similar to the screenshot below.
You can click through on the workflow name to see more details while it's running, and after a minute or two you should see that the workflow is terminated due to our failing test.
Additionally, as the owner of the repository, you should also get an email notification to tell you that the workflow failed, and everyone who browses the repository will see a red cross symbol next to the commit in the Git history.
Fixing the code
Let's fix our codebase by updating the
sayHello() function to return the correct output, like so:
If you want, you can commit this change and push it…
… and you should see that the 'Audit' job in our workflow file now completes successfully and everything has a nice green check mark next to it.
Great! That's working really well and, from now on, any time someone makes a push or pull request to the
main branch, the tests and vetting and linter checks will be automatically run.
From here, you can extend the workflow to carry out more checks or send additional notifications if you want to — or even expand it to act as a continuous deployment (CD) pipeline that builds and deploys your binaries. To give you some ideas, here are a couple of slightly more complicated workflows from my own projects: