A Gentle Introduction to GitHub Actions

I recently helped a friend with GitHub Actions. It is a streamlined experience that you write code and run your code without leaving GitHub. The experience is ok (documentation is confusing). I am going to walk you through a setup for running a build of Rails app.

Example: Rails app

full example in gist

You create a file under .github/workflows/:filename.yml. You can name the Action by:

name: Build

And specify when do you want GitHub to take actions:

on: [push, pull_request]

Here we want the actions to be run on push and pull_request events. Other events can be found here.

Define to run your jobs to on Actions Visual Environment of latest Ubuntu (You can also choose Windows or macOS) with some environment variables:

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      RAILS_ENV: test

Set up for Postgres

Let‘s install a PostgreSQL 12.0 database:

services:
  postgres:
    image: postgres:12.0
    env:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: "postgres"
      POSTGRES_DB: postgres
    ports:
      - 5432/tcp
    options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

The envs are needed otherwise you‘ll hit a mysterious error: postgres service is unhealthy. The options are needed because the postgres image does not provide a healthcheck.

Steps

Checkout the project

Specify the steps under jobs, clone your project with actions/checkout:

jobs:
  ...

  steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 1

fetch-depth 1 makes it faster by doing a shallow clone.

By default it looks at on: in your workflow yaml and runs on these events.

Install softwares for previous postgres service:

- name: apt-get
  run: |
    sudo apt-get update -y
    sudo apt-get install -y libpq-dev postgresql-client

Note the -y option (--assume-yes) here. To prevent the step hanging because some software may need confirmation.

Setup Redis

- name: Set up Redis
  uses: shogo82148/actions-setup-redis@v1
  with:
    redis-version: '5.x'

Setup Ruby

The next step is to setup latest version of Ruby 2.7 (by ruby/setup-ruby, NOT official actions/setup-ruby):

- name: Set up Ruby
  uses: ruby/setup-ruby@v1
  with:
    ruby-version: 2.7

ruby/setup-ruby is from official ruby organization maintaining by Benoit Daloze: https://github.com/ruby/setup-ruby.

Bundler is already installed by ruby/setup-ruby. Let‘s set up the cache for gems. Supposed you have .ruby-version and testing single ruby:

- name: Cache gems
  uses: actions/cache@v1
  with:
    path: vendor/bundle
    key: bundle-use-ruby-${{ runner.os }}-${{ hashFiles('.ruby-version') }}-${{ hashFiles('**/Gemfile.lock') }}
    restore-keys: |
      bundle-use-ruby-${{ runner.os }}-${{ hashFiles('.ruby-version') }}-

- name: bundle install
  run: |
    bundle config path vendor/bundle
    bundle install --jobs 4 --retry 3
${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}

will look something like this

Linux-gem-j582u6485a807n8i603to05400fa82t4129424a9s136b38a6b191b092f9a1232

hashFiles is called a Function on GitHub Actions.

restore-keys is a pattern that matches above to find the right cache:

${{ runner.os }}-gem-

To test matrix of operating systems, change ${{ runner.os }} to ${{ matrix.os }}.
To test matrix of ruby, change ${{ hashFiles('.ruby-version') }} to ${{ matrix.ruby }}.

Setup front-end dependencies

Now is a good time to install our front-end dependencies:

- name: Get Yarn cache directory
  id: yarn-cache-dir
  run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Cache node modules
  uses: actions/cache@v1
  id: yarn-cache
  with:
    path: ${{ steps.yarn-cache-dir.outputs.dir }}
    key: ${{ runner.os }}-js-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-js-

We can reference the result from other step by id:

${{ steps.yarn-cache-dir.outputs.dir }}

Creating database

We can then create and migrate the database:

- name: Setup database
  run: bin/rails db:create db:migrate

Run! Run!

Run tests

- name: RSpec
  run: bin/rspec

Run rubocop:

- name: Rubocop
  run: bin/rubocop --parallel

The --parallel option to run cops in parallel.

That‘s it! You can see above full example in gist.

All in all, I feel things could be more abstract. For example, we should be able to say cache: true to let GitHub Actions handles the Bundler cache for us:

- name: bundle install
  cache: true
  run: |
    bundle install --jobs 4

More Actions Configurations

Where to find more actions

GitHub Marketplace > Actions

Real-world Scenarios

The main documentations of GitHub Actions are not jumping all over the places... But we as developers are good at trial and errors, so here is a list of Open Source Ruby / Rails apps that are using GitHub Actions for your reference:

Hope you find this helpful.

Til next time,
Juanito