Testing my dotfiles with Github actions

I went through a phase that involved reinstalling MacOS way, way too often. At a certain point I got fed up with installing things over and over again and decided to version my dotfiles. And so https://github.com/orf/dotfiles was born.

This reolved around a bootstrap.sh script that clones your dotfiles repository and does some git tricks to check out the contents to your home directory and keep the actual git repository under ~/.dotfiles. While this wasn’t an original piece of work (thanks Stack Overflow!), I did modify it to do a sparse checkout, making it ignore the README.md in the repository (nobody want’s a README.md file in your home directory).

This worked fantastically, until I needed to reinstall my machine again. At that point I found a few issues with the shell script, made some fixes and moved on with my life. However this isn’t ideal. You should have confidence that the bootstrap.sh will actually work, and continue to work!

Enter Github actions

I spent some time last week setting up a Github actions workflow for my dotfiles. This is the complete workflow:

on: [push]

name: CI

jobs:
  build_and_test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@master
      - name: Run bootstrap
        shell: bash
        run: |
          brew untap caskroom/versions
          ./bootstrap.sh
        env:
          REPO: ${{github.workspace}}
          DOTFILES_REF: ${{github.sha}}
          HOMEBREW_BUNDLE_BREW_SKIP: "node"

The bootstrap script clones the latest master to your home directory. This is ideal when bootstrapping a new machine, it’s not what you want when you are running a CI job. So I modified the script to pull from the local checkout being tested (that’s the REPO environment variable).

The script also needs a reference to check out due to the sparse checkout method I mentioned above, so the DOTFILES_REF variable makes the script check out the current commit being tested.

The MacOS environment isn’t signed into the Mac App Store which means that the mas dependencies would fail to install. I added a simple if condition to my .Brewfile (which is i just a Ruby file) to handle that if the CI environment variable is set:

# Github actions cannot install these.
unless ENV.has_key?('CI') then
    brew "mas"
    mas '1Password', id:1333542190
    # etc
end

A few thoughts on Github Actions

Speed

Gitlab actions are really quick to start. Unlike Travis MacOS builds start instantly.

Structure

Github actions have a really interesting way of working, which I think is a great balance between a “CI tool” and a general-purpose “actions” framework. Every step is a pre-built action, even checking out the branch being built:

- uses: actions/checkout@master

I really like this way of doing things. It lets you fit pre-built components together to make really interesting pipelines. For example there is an actions-rs organization with a number of workflows to test rust projects:

- name: Install Rust
  uses: actions-rs/toolchain@v1
  with:
    target: macos
- name: Run tests
  uses: actions-rs/cargo@v1
  with:
    command: test

HCL vs Yaml

Github actions are really, really awesome. When they where first introduced they used a rather weird HCL (i.e Terraform) syntax:

action "terraform-init" {
  uses = "hashicorp/terraform-github-actions/init@v<latest version>"
  needs = "terraform-fmt"
  secrets = ["GITHUB_TOKEN"]
  env = {
    TF_ACTION_WORKING_DIR = "."
  }
}

This kind of threw me off at first - it’s a bit weird and unclear. I think they understood that because they have since moved to a YAML structure:

jobs:
  on-pull-request:
    name: On Pull Request
    runs-on: ubuntu-latest
    - name: Terraform Init
      uses: hashicorp/terraform-github-actions/init@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: '.'
        AWS_ACCESS_KEY_ID:  ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY:  ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Coming from Gitlab-CI I much prefer this.