Managing Builds and Releases with GitHub Actions

December 2, 2019 - 4 min read
dev git github github actions

I've been a longtime user of Travis CI, specifically for a pet project managing gliding flights parsing and analysis - that in the past had iterations in Java and Python and has for several years been written in Go.

With recent talk about GitHub Actions and also seeing that a couple of new projects in the containers area seem to be using it, i decided to give this a go. The first results are pretty good and in this post i'll describe what's available and how to use this functionality to:

  • Build cross platform Go binaries, plus test and coverage
  • Build and publish Docker containers into registries
  • Automate GitHub release creation using tags, including generation of changelogs

Workflow

GitHub Actions allow the creation of workflows with each step automating some custom process. These can be building and packaging software, deploying to some external service, generating code quality reports, among many others. They are stored under .github/workflows and defined in yaml, and you can have as many as you want.

Workflows are based on events such as pushing new code, new branches or tags to a repository, opening or closing pull requests, commenting on a PR, etc. In my example i want to trigger for all branch updates, tags starting with v - my version tag convention - and i want to skip the workflow for doc file changes.

on:
  push:
    branches:
      - '*'
    tags:
      - 'v*'
    paths-ignore:
      - 'CONTRIBUTING.md'
      - 'README.md'
      - 'docs/**'

One of the best features of GitHub Actions is the Marketplace, where you can find reusable components for most common tasks. An example is the GH Release action to automate the creation of GitHub releases everytime a new tag is pushed.

  - name: Release cross platform binaries
    uses: softprops/action-gh-release@v1
    if: startsWith(github.ref, 'refs/tags/v')
    with:
      files: |
        _dist/**
      body_path: CHANGELOG.md
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

This step relies on softprops/action-gh-release@v1 and i pass it the list of assets that go with it (the previously built binaries for each platform) as well as the generated changelog. I found this simpler than relying on the official create-release action which requires a separate step to upload assets.

Notice that this step has a condition so that it is only triggered for version tags, and ignored for other tags as well as branch updates.

The build step it depends on relies on the wonderful gox tool which does cross compilation for multiple platforms. Another option would be to rely on Github Actions's matrix strategy to truly run the builds on multiple platforms, but this is rarely needed when building Go binaries.

  - name: Make build for cross platform binaries
    run: |
      make build-cross

Makefile Actions

In the last step above we simply call a make target. The decision to keep most of the build steps in a Makefile while there are equivalent GitHub Actions is to still be able to replicate these steps offline. Wrapping them as individual steps in the workflow is trivial.

Check the full Makefile here, heavily based on this one from the Helm project. Targets include build, build-cross, test, test-coverage, changelog, docker and docker-push. Here's the build-cross target from above.

.PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static"
build-cross: $(GOX)
	GO111MODULE=on CGO_ENABLED=0 $(GOX) -parallel=3 \
      -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)_{{.OS}}_{{.Arch}}" \
      -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' \
      ./cmd/goigc

Generating Changelog

The goal is to generate the changelog before creating the release, and include the result in the body text. I spent some time choosing an option for this, there are again multiple GitHub Actions for this task.

In the end i still relied on the github_changelog_generator with some built-in logic to track the previous tag, here's the Makefile target.

changelog:
	@./scripts/changelog.sh
cat scripts/changelog
...
github_changelog_generator -u ezgliding -p goigc --since-tag $(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)

Visualization

The great integration with the rest of GitHub is also very useful.

You can easily browse the workflows, check logs and retrieve any artifacts from the different steps.

It's been a positive transition and i don't think i'll move back.