Using GitHub Actions with Go

Krzysztof Kowalczyk
Aug 26 · 8 min read · 3933 views

GitHub Actions can replace CI services like Travis or CircleCI for Go projects hosted on GitHub.

This article describes how to use actions to build and test Go projects.

GitHub Actions will be fully released on Nov 13. To use them now you need to sign up for beta and wait to receive access.

What do Actions give you?

With actions you can run code on every checkin (and many other GitHub events).

Most common use for actions is Continuous Integration (CI) where you build and test code to make sure that new changes didn't introduce bugs.

When things fail you get notified by an e-mail.

You can see the logs of a particular run. Here's how the UI for browsing the logs looks like

GitHub actions support running arbitrary code so you can do much more than just build and test the code.

Basic workflow for Go

A workflow defines one or more jobs,. Each job consists of multiple steps, like checking out source code, installing Go toolchain, building and testing your Go code etc.

Workflows run on computers operated by GitHub. The service is free for public repos and pay-as-you-go for private repos.

This is the simplest workflow that builds and tests a Go package. Create .github/workflows/go.yml file in your GitHub repository:

name: Build and test Go
on: [push, pull_request]
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - name: Set up Go 1.13
        uses: actions/setup-go@v1
        with:
          go-version: 1.13

      - name: Check out source code
        uses: actions/checkout@v1

      - name: Build
				env:
					GOPROXY: "https://proxy.golang.org"
        run: go build .

      - name: Test
				env:
					GOPROXY: "https://proxy.golang.org"
        run: go test -v .

Let's deconstruct this.

Define the name of a workflow

name defines a name of the workflow, visible in Actions tab in GitHub's repository view.

name: Build and test Go

Specify what triggers a workflow

Workflow is triggered by an event in your GitHub repository. There are many event types, including a checkin, a creation of pull request etc.

on: [push, pull_request]

This means: execute workflow on checkin (well, when it's actually pushed to GitHub) and pull request.

This is a good default from most projects.

[push, pull_request] is an YAML array with 2 elements.

Specify OS executing the workflow

runs-on defines the OS of the machine running the workflow. Most common:

  • ubuntu-latest : ubuntu 18.04 (could be newer in the future)
  • macos-latest
  • windows-latest

Full list of available options.

Define a job

A job is a sequence of steps:

jobs:
  build:
    name: Build

We define only one job with id build whose human-visible name is Build.

We can define multiple jobs. Jobs run in parallel but they start in an empty environment, so if you have multiple steps, like build and test, it's faster to have them as part of a single job.

Install Go toolchain

- name: Set up Go 1.13
  uses: actions/setup-go@v1
  with:
    go-version: 1.13

The beauty of actions is that they are open-sourced and can be shared. actions/setup-go is an action hosted in https://github.com/actions/setup-go. If you need to tweak how an action works, you can fork it and improve it.

Actions can be either JavaScript (node.js) scripts or docker images. actions/setup-go is a node.js action.

name is human-readable name of the step.

Actions can accept arguments, which we provide using with. go-version is an argument that actions/setup-go understands. It defines which version of Go we want to install.

Checkout source code

- name: Check out source code
  uses: actions/checkout@v1

action/checkout is a built-in GitHub action (which means we can't see how it's implemented).

It checks out your repository to /home/work/<repo_name> directory e.g. repository github.com/kjk/notionapi would be checked out into /home/work/notionapi directory.

You can over-ride checkout directory with path argument (see full list of arguments).

Build and test Go code

- name: Build
	env:
		GOPROXY: "https://proxy.golang.org"
	run: go build .

- name: Test
  run: go test -v .

We can execute arbitrary commands in steps.

The most common steps for Go code are

  • building the code
  • running tests

Working directory when executing commands is the source code directory.

When a step fails, the workflow stops and you get an email.

For projects using module, go build will also download dependencies recorded in go.mod and go.sum.

Downloading modules via proxy is much faster than cloning repositories with git.

We explicitly specify GOPROXY for Go 1.12. In 1.13 it's set by default.

Module proxy is only supported by Go 1.12 and later.

Advanced features

Using secrets

Sometimes you need to use a secret that you can't expose to public. In this example we have a project that is deployed to a Netlify account.

In GitHub UI we defined a secret NETLIFY_TOKEN needed to authenticate with Netlify:

Here's how to use it:

- name: Netlify deploy
  env:
    NETLIFY_TOKEN: ${{ secrets.NETLIFY_TOKEN }}
  run: |
    ./netlifyctl -A ${NETLIFY_TOKEN} deploy || true
    cat netlifyctl-debug.log || true

We can access secrets in workflow .yml file as ${{ secrets.<SECRET_NAME> }}. We can pass as an argument to a command executed with run or, as in this example, set an environment variable use env.

Remember that stdout and stderr of executed commands is logged so be careful to not print secrets.

More about secrets.

Matrix builds

Sometimes you want to test on multiple operating systems or with using different versions of Go.

You can specify that with matrix builds.

Here's a way to run workflow on every supported OS:

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]

What happened here is:

  • we defined a variable os which is an array of 3 values
  • the variable is under strategy/matrix key which tells actions to run the workflow 3 times, setting variable ${{ matrix.os }}
  • we can reference that variable in values. In this case runs-on uses ${{ matrix.os }} to specify which OS to use to run the action

Here's a way to run workflow with multiple Go versions:

runs-on: ubuntu-latest
strategy:
  matrix:
    goVer: [1.11 1.12 1.13]
steps:
	- name: Set up Go ${{ matrix.goVer }}
	  uses: actions/setup-go@v1
	  with:
	    go-version: ${{ matrix.goVer }}

We defined goVer variable with 3 values and we use it to tell actions/setup-go@v1 which version of Go to install.

Those can be combined:

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    goVer: [1.12 1.13]
steps:
	- name: Set up Go ${{ matrix.goVer }}
	  uses: actions/setup-go@v1
	  with:
	    go-version: ${{ matrix.goVer }}

This will execute 6 different builds for a combination of 3 x 2 values.

Finding bugs with staticcheck

Static analysis can find bugs in your code, which is great.

staticcheck does static analysis on Go code and finds issues like unused functions and many more.

To run staticcheck locally, do:

go get -u honnef.co/go/tools/cmd/staticcheck
staticcheck ./...

To run staticcheck on GitHub Actions CI:

- name: Staticcheck
  run: |
    # add executables installed with go get to PATH
    # TODO: this will hopefully be fixed by
    # https://github.com/actions/setup-go/issues/14
    export PATH=${PATH}:`go env GOPATH`/bin
    go get -u honnef.co/go/tools/cmd/staticcheck
    staticcheck ./...

Uploading artifacts

When a CI job is finished, the server is destroyed. Sometimes we want to preserve files created during CI run. We can do it with a built-in upload-artifact action:

- uses: actions/upload-artifact@master
  with:
    name: my-artifact
    path: path/to/dir/with/artifacts

Uploaded artifacts will be accessible via web UI:

Cron: running workflows periodically

You can schedule workflows to be executed periodically e.g. once a day.

Define a workflow in a new .yml file in .github/workflows directory and schedule it to be periodically executed:

on:
  schedule:
    # * is a special character in YAML so you have to quote this string
    - cron: "0 4 * * *"

cron field uses cron syntax.

The order of fields is:

  • minute (valid values: 0-59, 0 in the above example)
  • hour (valid values: 0-24, 4 in the example)
  • day of the month (valid values: 1-31, * means "every day)
  • month (valid values: 1-12, * means every month)
  • day of the week (valid values: 0-6, 0 is Sunday)

In the above example we schedule the workflow to run at 4:00 am every day, UTC time.

The syntax is quirky so use crontab guru to create / verify it.

Running multiple commands

You run multiple commands in a single step with |:

- name: Staticcheck
  run: |
    # add executables installed with go get to PATH
    # TODO: this will hopefully be fixed by
    # https://github.com/actions/setup-go/issues/14
    export PATH=${PATH}:`go env GOPATH`/bin
    go get -u honnef.co/go/tools/cmd/staticcheck
    staticcheck ./...

Making run cross-platform

Keep in mind that commands executed with run are executed in an os-specific default shell (bash on Linux / Mac OS X and cmd.exe on Windows).

Simple commands like go build work the same in both shells, but e.g. export PATH=${PATH}:`go env GOPATH`/bin is specific to bash.

All OS-es have Python and Node installed, so if you need to execute a longer logic, write it in Python or Node.

For example, if you have foo.js script:

- name: Run foo.js
  run: node foo.js

See more information about shells.

Debugging workflows

Workflows are running on remote servers so if something goes wrong, it might be difficult to figure out why.

Here are few tips:

  • read about the setup of the machines (file system layout, environment variables) and double-check assumptions in your script
  • if you're uncertain, add more logging. E.g. if you're not sure if the current directory is the one you expect, log it before error happens (echo "current directory: `pwd`" on Linux)

More Go resources

  • Essential Go is a free, comprehensive book about Go that I maintain

Go programmer for hire

If you're looking for a Go programmer for contract work, let's talk.

Upadating...

Share on