Exploring the shiny new GitHub Actions

We recently got access to GitHub Actions, a new tool provided to private repositories (during the beta phase). To test it, we found a perfect use case matching a real problem in our deployments.

Ruby tooling

As you may have noticed, we mainly use Ruby for our applications, and recently Bundler1 got updated to 2.0.
The bundle install command reads the Gemfile2, fetches all required libraries (gems) and generate a Gemfile.lock associating each library with a tagged release in order to block a locked libraries configuration. Finally the bundler version used to build this configuration is added at the end of the file.

However, many hosting platforms did not support the 2.0 upgrade from the get go, and expect the Gemfile.lock from our code to be on 1.x format, or a least not to include a ‘BUNDLED WITH 2.x’ statement.

So before pushing our code to our deployment platform, we have to ensure that this ‘BUNDLED WITH’ statement is absent from the Gemfile.lock file: A perfect use case to test GitHub Actions!

A simple use case

GitHub Actions heavily stands on Docker for managing its operations. Actually each action you describe in a repository workflow consists of folder including at least a Dockerfile. You can have as many actions as you need, chaining of pararellize them. Each action mark completion or failure with an exit code. The repository code is automatically associated to the created container.

In our case, all we have to do is creating a folder, including a Dockerfile describing how we want to check for “BUNDLED WITH” absence within our Gemfile.lock file. We added .github/action-check-gemfile-lock/ folder to our repo containing these files:

FROM debian:stable-slim

LABEL "com.github.actions.name"="Check Gemfile.lock"
LABEL "com.github.actions.description"="Check Gemfile.lock confirmity for PaaS deployment"
LABEL "com.github.actions.icon"="tag"
LABEL "com.github.actions.color"="blue"

LABEL "maintainer"="Bob Maerten <[email protected]>"
LABEL "version"="1.0.0"

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh

! grep 'BUNDLED WITH' Gemfile.lock

Then, in order to make this run, we describe the workflow:

workflow "on each push, run checks on repo's code" {
  on = "push"
  resolves = ["check for BUNDLED WITH absence in Gemfile.lock"]
}

action "check for BUNDLED WITH absence in Gemfile.lock" {
  uses = "./github/action-check-gemfile-lock/"
}

As we want the container to exit with a 0 status if the content is absent, we use the shell negation operator (!) to reverse grep command result.

Testing the result

GitHub Action Success

Cheking the logs of our container

<stripping all docker building image logs...>
[...]
Running '! grep -q 'BUNDLED WITH' Gemfile.lock'...
Successfully ran '! grep -q 'BUNDLED WITH' Gemfile.lock'

Also note that the GitHub Actions are accounted for pull requests checks, so if our action fails, the pull request is not marked good for merging.

GitHub Checks Passed

Why reinvent the wheel?

Now that we understand who all of this work, and that our test pass, why not look for a way to optimize?
Well, our test case fits in a sole shell command, but we action folder we added is only usable within this repository.

We could extract it to an external repo and reference it within the workflow by its GitHub short address:

workflow "on each push, run checks on repo's code" {
  on = "push"
  resolves = ["check for BUNDLED WITH absence in Gemfile.lock"]
}

action "check for BUNDLED WITH absence in Gemfile.lock" {
  uses = "levups/[email protected]"
}

But it comes handy that GitHub provides for a bunch of readymade actions for us to use, giving this workflow:

workflow "on each push, run checks on repo's code" {
  on = "push"
  resolves = ["check for BUNDLED WITH absence in Gemfile.lock"]
}

action "check for BUNDLED WITH absence in Gemfile.lock" {
  uses = "actions/bin/[email protected]"
  args = ["! grep -q 'BUNDLED WITH' Gemfile.lock"]
}

GitHub Actions: ✓ checked, with extreme caution!

From this simple test, we can say this workflow-thing could basically replace a bunch of checks handled at the moment by external continuous integration/deployment tools. Discarding the extra value related to code analysis, a private repository could only rely on itself for running CI/CD within a GitHub Action.

But beware! Running GitHub Actions on your private repos from external action repositories is like giving the keys of your house to a stranger, especially by tagging the relatad action repo with @master. Who knows if someone, someday, somehow, would not be tempted to push some tracking code on this repo? So our best bet is to keep those actions close to us, in a repo we own or inside the project repo like we did at the start of this post. At least, please use a tag/commit you manually reviewed. This way, if the action changes or is corrupt, you will keep using the version you deemed safe.

Beside of this, actions are not limited to git push. Actions can be triggered by many events like issue creation, pull request reviews, project card movement… We are eager to test further and see how all of this will evolve.


  1. Ruby packaging tool, see https://bundler.io 

  2. Ruby librairies file list used by Bundler