Haskell on Actions, Part 2

by @pbrisbin on May 20, 2021

This is the second post in a series about our Haskell projects on GitHub Actions.

At Freckle, we strive to automate as much of our processes as possible; so in this post, we’ll be updating our library example to auto-release to Hackage, and add a new example for executable releases.

  1. Building a simple project including caching concerns
  2. Automated releases of libraries or executables :point_left:
  3. Docker-based deployments from Actions

Automated release

For our OSS libraries, we want to automate our Hackage releases, but still have a Human involved in choosing version and updating CHANGELOGs. To support this, we define a Workflow to run another custom Action when a tag is created starting with v:

.github/workflows/release.yml:

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: freckle/stack-upload-action@main
        env:
          # https://docs.github.com/en/actions/reference/encrypted-secrets
          HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}

Given this, our release process is:

  1. Open a PR bumping version and authoring CHANGELOG
  2. Seek review and merge the PR
  3. Push the correct v{version} tag:

    git tag -s -m vA.B.C.D vA.B.C.D
    git push --follow-tags
    

Automatic release

Step (3) above is a bit tedious and actually error-prone (if you push a tag that doesn’t match what’s in the latest package.yaml). So we’re starting to experiment with automating away that part too.

haskell-tag-action helps accomplish this. The idea is to infer version from package.yaml and, if one doesn’t yet exist, push a version tag. My original intent was to have that trigger our existing release.yml, but GitHub doesn’t allow actions taken in one Workflow, using the built-in token, to trigger other Workflows. (How dare they infringe on my right to trigger infinite Workflow loops!) So instead, we just do it in release.yml:

on:
  push:
    branches: main

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - id: tag
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        uses: freckle/haskell-tag-action@main

      - if: ${{ steps.tag.outputs.tag }}
        env:
          HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
        uses: freckle/stack-upload-action@main

Now, our release process is:

  1. Open a PR bumping version and authoring CHANGELOG
  2. Seek review and merge the PR

:tada:

Executables

One of our Haskell projects is an internal CLI, so we build binaries for Linux and OSX in response to a tag being pushed (we haven’t yet reached automatic release for this, only automated):

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  create-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/create-release@v1
        id: create-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false
    outputs:
      upload_url: ${{ steps.create-release.outputs.upload_url }}

  upload-assets:
    needs: create-release
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            suffix: x86_64-linux
          - os: macOS-latest
            suffix: x86_64-osx

    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: freckle/stack-cache-action@main

      # Actual build steps ellided
      - run: make dist/my-tool.tar.gz

      - uses: actions/upload-release-asset@v1
        id: upload-release-asset
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.create-release.outputs.upload_url }}
          asset_path: ./dist/my-tool.tar.gz
          asset_name: my-tool-${{ matrix.suffix }}.tar.gz
          asset_content_type: application/gzip

For internal applications which are not libraries or executables, our automated releases involve building Docker images and updating AWS services. That’ll be the subject of our last and final post in this series. Stay tuned!