From TravisCI to GitHub Actions

GitHub Actions

If premature optimization is the root of all evil, should procrastination until the right tool arrives be divine? If so, then yours truly would be the Patron saint of pragmatic & lazy programmers everywhere. I'm no adoption curve laggard, rather more like an early majority adopter with an extended view of the x-axis 😅 and I'm here to convince you that now is the right time to reevaluate your CI/CD needs.

GitHub Actions was announced last October and I never found the time to experiment with it. Last month they announced free access for all public repositories along with a few revamped features. It looks way more intriguing and given the long Labor Day weekend it felt like a great time to learn GitHub's new CI/CD killer app with my favorite open source gem minitest-spec-rails.

⚠️ GitHub Actions is in Beta access and will be generally available in November.

Why GitHub Actions?

The value of Continuous Integration (CI) via tools like TravisCI & CircleCI have benefits that are immediately obvious. Constant test feedback in pull requests give us the confidence to move our projects forward at a faster pace.

But what about the Continuous Delivery (CD) part of CI/CD? In a world driven by containers and microVMs, how are we to ship our applications to Cooper Netties without a good pipeline? Continuous delivery is what powers this and like others I think ​GitOps solutions tightly integrated with GitHub are important to helping modernizing all of our workflow needs.

Here is a quick list of things I found compelling about GitHub Actions.

Migrating From TravisCI

Enough hype and reasoning. Let's dig into my notes on how I setup my first CI workflow with GitHub Actions.

GitHub Actions Getting Started w/Ruby Workflow #1
GitHub Actions Getting Started w/Ruby Workflow #2

I used their Ruby example workflow as my starting point. From there I changed the name of the workflow to ci.yml to better reflect my intent. Note how all workflows will be in the repo's .github/workflows directory. The defaults were simple, basically checkout, bundle, and test using their latest Ubuntu virtual environment as the workflow's runner. Commit and see what happens... woohoo, our first error!

sqlite3.h is missing. Try `brew install sqlite3,
'yum' install sqlite-devel' or 'apt-get install libsqlite3-dev'

sqlite3.h is missing error in GitHub Actions Ruby workflow

This was easy to fix by adding sudo apt-get install libsqlite3-dev prior to our Bundle command. After that everything turns green and we have clutched our first duos win with GitHub Actions by our side. 🎉

Happy Little CI

This is your world and you can do anything you want, so why stop there? What about testing different Ruby versions? Another common pattern requires that we make sure our gem is compatible with multiple versions of downstream gems. I often reach to Thoughtbot's appraisal gem which allows us to generate multiple Gemfile variants.

To pull this all together we are going to need to jump into GitHub Actions' support for matrix builds. We are also going to make this CI workflow feel more personalized and organized to suit our personal needs. Here is my final .github/workflows/ci.yml workflow file.

name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby:
          - '2.5.x'
          - '2.6.x'
        rails:
          - 'rails_v5.1.x'
          - 'rails_v5.2.x'
          - 'rails_v6.0.x'
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - name: Setup System
        run: |
          sudo apt-get install libsqlite3-dev
      - name: Setup Ruby
        uses: actions/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
      - name: Bundle
        env:
          MTSR_RAILS_VERSION: ${{ matrix.rails }}
        run: |
          export BUNDLE_GEMFILE="${GITHUB_WORKSPACE}/gemfiles/${MTSR_RAILS_VERSION}.gemfile"
          gem uninstall -aIx bundler
          gem install bundler -v 1.17.3
          bundle install --jobs 4 --retry 3
      - name: Test
        run: bundle exec rake

Final Minitest Spec Rails GitHub Actions Pull Request FeedbackA few highlights on why I landed on the specifics of this workflow above.

Naming

Renamed the top level name from Ruby to CI. This is what shows up when you browse all Actions in GitHub's user interface. Keep the name logical to the workflow.

Each step can have a name attribute as well. By default the checkout action does not so I added one. Again these step names show up as headers in the log view. So make them logical to you or your team. I ended up with:

Context

GitHub Actions has several context objects that can be used from setting environment variables to expressions. Here I am using the matrix context but there are others like github, job, steps, secrets, and more.

Hacks

Lastly, I have standardized on Bundler 1.17.3 until a few v2 kinks are worked out. Feel free to disregard that step's run line to downgrade bundler.

The Rough Edges

So thats the spin. But any new product, especially beta access, is bound to give you a little grief. I certainly hit a few while learning today. None I would consider blockers to adopting GitHub Actions in your test projects or setting your sights for its eventual release. But here are the ones I found.

Match Your Workflow's File & Name Early!

Final Minitest Spec Rails GitHub Actions Pull Request FeedbackWhen I created my workflow using the "Ruby" example, I knew I wanted it to be called "CI". However, I neglected to set the top level name property early on and even renamed it a few times. This means I had a few names tied to the ci.yml workflow file and I'm guessing they will be forever burned into my repo's action link history. Oh well, I'll skeert more carefully on the next project.

Pull Request Usability

One nice feature of TravisCI is that they make it easy to monitor a pull requests build state when new commits are pushed. For GitHub Actions, I found myself constantly watching a build fail, clicking back to my PR, and then finding the new build links again. It would be nice if the build page detected a push to the same PR and navigated you to the new build automatically.

Environment Variables & Contexts

Providing runtime reflection to your Workflows & Actions is critical to enabling developers to build abstract and dynamic automation tools. GitHub does a great job by providing both environment variables and context objects. However, I found it hard to setup my builds BUNDLE_GEMFILE environment variable. I tried using their join function to do this in one step but found myself wishing that the GITHUB_WORKSPACE environment was a property on the github context instead. So maybe if github.workspace was there my join function would have worked?

Ruby Versions

The software in each virtual environment is impressive! For example at the time of this writing the Ubuntu environments have Ruby 2.3.7, 2.4.6, 2.5.5, and 2.6.3 already installed. When I took a closer look at the actions/setup-ruby repository I found they basically do some shebang path switching to use one of these preinstall Ruby versions. I totally see the value in this but expect if finer grain control is needed you would use one of two solutions:

Badge Support

Minitest Spec Rails GitHub Actions Badge ExampleWe all want to show off our workflow's status in our project's README file. After some digging I found this Shields.io GitHub issue discussion which found the magic formula to generate an SVG using GitHub Actions. How cool is this!?! Native badge support shows they are in it to win it.

https://github.com/{USER}/{REPO}/workflows/{NAME}/badge.svg

More CD 🔔

Thanks for taking the time to read my post today on GitHub Actions. Got a fever for more continuous delivery examples? Stay tuned to @CustomInkTech on Twitter for updates. Next week I'll be writing about how to use GitHub Actions to deploy your Rails applications to AWS Lambda using Lamby.

Additional Resources 📚

New to GitHub Actions? I found these resoruces super helpful. You will almost certainly want to bookmark the developer documentation!

by Ken Collins
AWS Serverless Hero