Converting TravisCI Jobs to GitHub Actions (Revisited)

Submitted by david.stinemetze on Tue, 08/27/2019 @ 8:30am

Back in January, I was granted early beta access to GitHub Actions. At the time, I wrote a blog post about Converting TravisCI Jobs to GitHub Actions. GitHub has since opted to switch from HCL to YAML, so I've decided to revisit that post with some updated information.

What is GitHub Actions?

GitHub Actions is workflow automation built directly inside GitHub. This means a lot of CI functionality, commonly handled by tools like TravisCI and Jenkins, can now be managed using GitHub directly. Actions use docker to execute relevant tasks inside of containers. You can create multiple workflows that do different things for different events, such as:

  • push - "Triggered on a push to a repository branch. Branch pushes and repository tag pushes also trigger webhook push events. This is the default event."
     
  • pull_request - "Triggered when a pull request is assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review, locked, unlocked or when a pull request review is requested or removed. See below to learn how this event works with forked repositories."
     
  • release - "Triggered when a release is published, unpublished, created, edited, deleted, or prereleased."

Getting Started

For anyone new to GitHub Actions, the Automating your workflow with GitHub Actions documentation is a great starting point.

For the sake of parity with my previous post, I'm continuing to test using the drupal-patch-checker composer plugin I built previously for my Patching Production Drupal Sites With hook_update_N() is Risky post. It's a very small PHP package that currently uses TravisCI to perform simple code-style analysis and unit testing. 

Current TravisCI Configuration

TravisCI - drupal-patch-checker

This is my current TravisCI configuration:

language: php
php:
  - 7.1
  - 7.2
env:
  - CMD="test"
  - CMD="phpcs"
install:
  - "composer install"
script:
  - "composer $CMD"

If you are unfamiliar with TravisCI, essentially this runs composer test and composer phpcs using both PHP 7.1 and 7.2.

In my composer.json file, those scripts are defined as:

    "scripts": {
        "test": [
            "vendor/bin/phpunit --testsuite=DrupalPatchChecker"
        ],
        "phpcs": [
            "vendor/bin/phpcs --standard=vendor/drupal/coder/coder_sniffer/Drupal/ruleset.xml src/ tests/"
        ]
    }

Creating Workflows & Actions

In the current iteration of Github Actions, workflows are created using YAML. All workflows live in a .github/workflows folder located inside the main folder of the repo. A workflow consists of one or more jobs, which can perform one or more steps. Each step can perform simple commands or run actions. You can use either external actions or define your own internally. Actions can happen either sequentially or in parallel depending on how you define them.

According to the About Actions documentation, all actions can be built using Docker or Javascript. For the sake of this post, I will focus on using Docker, but if Javascript actions are possible, they may be a better solution as they don't require a separate virtual environment, so they can run much faster.

My workflow ended up looking like this:

name: Main Workflow
on: [push, pull_request]

jobs:
  unit_tests:
    name: Unit Tests
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master
    - name: Perform phpunit tests
      uses: ./actions/test
  code_style:
    name: Code Style Analysis
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master
    - name: Analyze code style
      uses: ./actions/phpcs

Looking at the above we can see I have a workflow that I've called Main Workflow. It is set to run on every push to the repo, and every pull request.

There are two jobs that run in parallel:

  1. Unit Tests
  2. Code Style Analysis

Each job has the required checkout step, and a subsequent step that references custom actions that we will define below.

It's important to note that individual steps within jobs share an environment, while the jobs themselves do not. This means, that you can't run build commands in one job and then work with those files in a subsequent job. This is a departure from the HCL version of GitHub actions, which I had documented in my previous post

Code Style Analysis Action

This action performs code style analysis on code in the codebase. 

actions/phpcs/action.yml:

name: 'phpcs'
description: 'Perform code-style analysis'
author: 'David Stinemetze'
runs:
  using: 'docker'
  image: 'Dockerfile'

actions/phpcs/Dockerfile:

FROM php:7.2-cli
RUN apt-get update
RUN apt-get install -y git zip unzip
COPY --from=composer /usr/bin/composer /usr/bin/composer

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

actions/phpcs/entrypoint.sh:

#!/bin/sh -l

sh -c "echo '---Installing dependencies---'"
composer install

sh -c "echo '---Running code style analysis---'"
vendor/bin/phpcs --standard=vendor/drupal/coder/coder_sniffer/Drupal/ruleset.xml src/ tests/

Unit Tests Action

This action performs phpunit tests on the codebase.

actions/test/action.yml:

name: 'test'
description: 'Perform phpunit tests'
author: 'David Stinemetze'
runs:
  using: 'docker'
  image: 'Dockerfile'

actions/test/Dockerfile:

FROM php:7.2-cli
RUN apt-get update
RUN apt-get install -y git zip unzip
COPY --from=composer /usr/bin/composer /usr/bin/composer

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

actions/test/entrypoint.sh:

#!/bin/sh -l

sh -c "echo '---Installing dependencies---'"
composer install

sh -c "echo '---Running unit tests---'"
vendor/bin/phpunit --testsuite=DrupalPatchChecker

PHP Version Discaimer

In TravisCI, I was running both PHP 7.1 and PHP 7.2 compatibility tests where this isn't quite doing that. For the sake of simplicity I've opted to just test PHP 7.2. But it would be easy enough to clone these actions and change the Dockerfile to load different versions of PHP or install a tool like phpenv which would allow for a more robust PHP versioning mechanism. 

Running the Actions

After a bit of trial-and-error I got everything up and running:

GitHub Actions YAML Run

On the left-hand side, you will see that Main Workflow ran on both push and pull_request events, which is expected as I opened a pull request on my own repo. If an external user made a pull request against my repo, using a separate fork, we should only see the pull_request event fire. 

Making Code-Style Analysis and Unit Tests Fail

I proceeded to create three separate PRs that cause the job to fail:

  1. Both jobs failing
    All checks are failing
    Both Jobs failing
     
  2. Just the code style job failing
    Just code style analysis is failing
    Code style failing
     
  3. Just the unit test job failing
    Just unit tests are failing
    Unit test failing

This behavior is what we'd expect from a CI platform, and is a lot closer aligned with the behavior we had in TravisCI than the behavior we got back in January.

Final Thoughts

I've still only scratched the surface of what you can do with GitHub Actions. This new iteration has a ton of new features that I'll continue to experiment as time permits. While I'm still not quite ready to rely on GitHub Actions for any of my critical production systems, I'm feeling good about the direction they are heading and might be willing to start switching over some of my less critical applications.