Skip to main content

continuous integration with go and github actions

·7 mins
Checkout my previous article on how to use private Go modules in local development and GitHub Actions workflows here.
Checkout my latest post on how to Continuous Deployment with Go and GitHub Actions here.

Introduction #

In a previous article, I explored how to use private Go modules in local development and GitHub Actions workflows. I did not cover how to use GitHub Actions to create a Continuous Integration (CI) workflow for a Go project. In this article, I’ll expand on that and explore how to use GitHub Actions to create a Continuous Integration (CI) workflow for a Go project.

Getting Started with Go #

To get started we first need a Go project. In this section we’ll delve into setting up a Go project and creating a simple Go application. I’ll create a new Go project and initialise a new Go module.

❯ go mod init github.com/aranw/go-github-actions-example

This will create a new Go module in the current directory. We can then create a new Go file and add some code.

Here we have the main.go file:

package main

import "fmt"

func main() {
    msg := sayHello("Aran")
    fmt.Println(msg)
}

func sayHello(name string) string {
    return fmt.Sprintf("Hello %s", name)
}

This is a simple Go application that prints “Hello Aran” to the console. Giving us a simple project to work with. We can also add a test file for our project.

Here we have the main_test.go file:

package main

import "testing"

func TestSayHello(t *testing.T) {
    msg := sayHello("Aran")
    want := "Hello Aran"

    if msg != want {
        t.Errorf("got %s; want %s", msg, want)
    }
}
As an example for later I’m going to omit the contents of the main_test.go for testing to demonstrate what a failed GitHub Workflow looks like!

This is a very simple example but it gives us a good starting point for our Continous Integration workflow with GitHub Actions.

Creating a GitHub Actions Workflow #

GitHub Actions is a powerful automation tool integrated within GitHub’s ecosystem, offering a seamless way to automating workflows directly within your repository. In this segment we will look at using GitHub Actions to create a Continuous Integration (CI) workflow specifically tailored for our Go project.

GitHub Actions facilitates a variety of automation tasks within the software development lifecycle, from simple code linting to complex deployments. For Go developers, this means leveraging GitHub’s infrastructure to automate testing, building and deploying applications. For this article, we will focus on creating a simple CI workflow for our Go project and in future articles we will explore complex workflows like deploying to a cloud provider.

Creating a Workflow File #

Typically the workflow file is a written in YAML and is stored in the .github/workflows directory in the root of your repository. The workflow file contains the definition of the workflow, including the triggers, jobs and steps. A repository can have multiple workflow files, each defining a different workflow. Advanced options can be to manage these workflow files in separate repositories or using an intermediate language like Cue or Jsonnet to generate the workflow files. In future articles we will explore these advanced options.

For our Go project we will create a new workflow file called ci.yml in the .github/workflows directory. This file will contain the definition of our CI workflow.

name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Set up Go 1.21
        uses: actions/setup-go@v5
        with:
            go-version: 1.21

      - name: Run Tests
        run: go test -v ./...

Right now in the workflow file we have defined a simple workflow that runs on every push and pull request to the repository. The workflow contains a single job called build that runs on the latest version of Ubuntu. The job contains three steps:

  • First we checkout the repository codebase into the runner.
  • Then we setup the runner with Go 1.21 using the actions/setup-go action.
  • Finally we run the tests using the go test command.

Now we are only testing our code in the above example and for a more complete Continuous Integration workflow we should also include static analysis, linting and building. We can add these steps to our workflow file to create a more complete CI workflow. Using the golangci-lint action we can easily add linting and other static analysis tools to our workflow.

Ideally this step should be done before running the tests. This is because we want to catch any issues with our code before running the tests.

  - name: golangci-lint
    uses: golangci/golangci-lint-action@v3
    with:
        version: v1.54

By default golangci-lint comes with some sane defaults but you can also configure it to your liking by adding a .golangci.yml file to the root of your repository. For more details on how to configure golangci-lint checkout the official documentation here.

We can also add a step to build our Go application.

  - name: Build
    run: go build -v ./...

Now that we have a complete CI workflow we can commit the workflow file to our repository and push it to GitHub.

GitHub Actions Failed Workflow

This is a good example of a failing Workflow. In this example the Workflow failed to due to the contents of the test file being missing. Now I’ll add the contents to the test file and push the changes to GitHub.

GitHub Actions Workflow Overview

From the overview page we can see both our failed and passed workflows. We can click on the workflow to see the details of the workflow.

GitHub Actions Workflow Details
In this screenshot you’ll see some warnings about Node.js 16 actions being deprecated. This comes from the Golang CI Linter action and has been fixed in the project’s master branch but v3 is still the latest tagged version!

Now we have a complete CI workflow for our Go project. We can use this workflow to test our code on every push and pull request to the repository. We can also use this workflow to run other tasks like building and deploying our Go application which we will explore in future articles.

To checkout the complete workflow file and example Go project you can visit the GitHub repository here.

Conclusion #

In this article, we explored how we can use GitHub Actions to set up a Continuous Integration (CI) workflow for a Go project. The journey towards automating and optimising our development workflows is both exciting and immensely rewarding. When introduced into a project, CI/CD practices can significantly enhance the quality, reliability, and efficiency of the software development lifecycle.

Implementing continuous integration with GitHub Actions brings significant advantages to Go projects and their development teams. It nurtures a culture of transparency and real-time collaboration, significantly reducing integration issues and fostering a proactive approach to bug fixes and code enhancement. Automated workflows provide instant feedback on changes, streamlining the development process by minimizing manual errors and saving valuable time. This level of automation accelerates the development cycle, enabling teams to focus more on innovation and rapidly deliver value to their users. Ultimately, GitHub Actions and Continuous Integration empower development teams to uphold high quality standards, enhance productivity, and facilitate smoother collaboration.

While we have touched upon the basics of integrating GitHub Actions with Go, there are several advanced patterns and practices that we have not covered in this article. Here are a few topics that I plan to cover in future articles:

  • Continuous Deployment: Beyond continuous integration, automating the deployment processes ensures that applications are seamlessly and reliably deployed. Future articles will delve into setting up continuous deployment workflows, enabling Go applications to be deployed automatically to various environments, including staging and production.
  • Guarding Pull Requests: Ensuring that only high-quality, tested code makes its way into the project’s main branches is crucial. We’ll explore strategies for guarding pull requests, such as requiring status checks to pass before merging and implementing automated code reviews, to maintain code quality and stability.
  • Versioning: Managing application versions in a systematic and automated manner is key to tracking releases and ensuring consistency across environments. Future articles will look at how we can automate this process with GitHub Actions workflows, facilitating better release management and compatibility tracking.
  • Local Development: We’ll explore how to test GitHub Actions workflows locally, enabling developers to validate their workflows before pushing changes to the repository. Using the nektos/act tool, we can simulate GitHub Actions locally, ensuring that our workflows are functioning as expected.

For a more in-depth understanding of GitHub Actions and how to leverage it for Go projects, I recommend exploring the official docoumentation for GitHub Actions. The documentation provides comprehensive guidance on setting up workflows, creating custom actions, and integrating with various tools and services.