Sorry! Your browser is not supported on this site and it might be acting a bit wonky. Please use Firefox, Chrome or Edge instead

A first impression of GitHub Actions CI/CD

Written by:
Rami Haddad

In November 2019 at GitHub Universe, several new products were introduced. Among them was the announcement of GitHub Actions. Actions is a tool within GitHub that enables continuous integration and a wide range of automation.

 

It’s a clear response to the introduction of similar tools by other vendors. In this post, I’ll give you a first impression based on desk research and hands-on testing. I haven’t compared GitHub Actions to its potential competitors.

Introducing GitHub Actions

I found Actions easy to pick up as I’m familiar with various CI systems such as Travis CI and CircleCI. What they share in common is the markup language YAML. GitHub Actions supports the three major operating systems, Windows, MacOS and Linux, and you can run any programming language supported by those.

Apart from being triggered by pull requests and commits, actions allows you to respond to any GitHub event. It allows you to trigger certain GitHub Actions workflows (including open source actions) based on:

  • the creation of issues
  • comments
  • the joining of a new member to the repository
  • changes to the GitHub project board.

Actions comes with a strong level of integration with GitHub, removing the requirement of an additional vendor for CI. From a business perspective, it is a clear response to GitLab and their CI offering as well as Azure DevOps.

Syntax

In the code below, I’ve showcased a workflow that is defined to trigger on ‘push’, meaning every commit of code to our repository. Our specific workflow configures Golang and its dependencies, then executes the binary file which prints out a “Hello World!” statement.

Actions comes with live-streaming logs during builds, providing direct feedback with color coding including code folding for clarity of code.

Workflow-run logs can be viewed live as seen below. This particular output comes from a workflow that contains one job which sets up Go, gathers and installs the Golang dependencies and then runs the run.go file, this file contains a piece of code which prints a “Hello, World!” statement.

Workflow-run logs can be viewed live as seen below. This particular output comes from a workflow that contains one job which sets up Go, gathers and installs the Golang dependencies and then runs the run.go file, this file contains a piece of code which prints a “Hello, World!” statement.

name: Go
on: [push]
jobs:

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:

    - name: Set up Go 1.12
      uses: actions/setup-go@v1
      with:
        go-version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@v1

    - name: Get dependencies
      run: |
        go get -v -t -d ./...
        if [ -f Gopkg.toml ]; then
          curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
          dep ensure
        fi

    - name: Greeting
      run: go run run.go

    - name: Build
      run: go build -v .

The above code snippet was taken from my GitHub account.

Community-powered workflows

Publicly available workflows (called community-powered workflows) are being created by GitHub’s large community, enabling reusable workflows. This community effort results in the development of high-quality code that has been tested and reviewed by a wider community and is published on the GitHub marketplace.

A notable positive side-effect of this is that the workflow will be optimised continuously.

Its list of actions are growing at a quick pace. This could prove a major software development time-saver as developers won’t have to reinvent the wheel every time. A separate GitHub independent community of shared GitHub Actions also exists and has been growing consistently as developers have shown interest in shared workflows.

Both GitHub marketplace and the Netlify-based workflow-sharing community are public, allowing everyone to contribute and make use of the actions. Organizations seeking to share workflows privately will have to share these through self-created repositories on GitHub.

The repository will then have a ‘Include action in your project’ button which allows you to simply add the action to the repository of your choice, thus workflows are both publicly and privately shareable.

Persistence of workflow data through artifacts

GitHub Actions supports the persistence of workflow data through artifacts. A workflow that may contain multiple jobs will be able to persist and process data from previous jobs. This enables developers to optimize workflows and write cleaner, more efficient code.

In the code below, I showcase this with an example. The workflow consists of three jobs which require one another and shows the ability of code reuse.

The artifact can be downloaded after the workflow run completes, and in this case it is a .txt file containing the string “Hello World!” which was printed in shell to stdout using two separate jobs and saved to the artifact file during each job.

The downside is the inability to solely retrieve this artifact through the GitHub API, the API only allows you to download the complete archive output. The artifacts’ functionality works across operating systems, as is shown in the example.

Jobs run in parallel by default. In this instance, I defined dependencies on other jobs causing a sequential run.

#modified
name: Go
on: [push]
jobs:
#
  job_1:
    name: job_1 demonstrates the print of a String
    runs-on: ubuntu-latest
    steps:
      - shell: bash
        run: |
          echo "Hello" > string-print.txt
      - name: Upload stdout result for job 1
        uses: actions/upload-artifact@v1
        with:
          name: stringPrint
          path: string-print.txt

job_2:
  name: job_2 demonstrates the print of another String value
  needs: job_1
  runs-on: windows-latest
  steps:
    - name: Download data from job_1
    uses: actions/download-artifact@v1
    with:
      name: stringPrint
    - shell: bash
      run: |
        value=`cat stringPrint/string-print.txt`
        echo $value "World!" > stringPrint/string-print.txt
    - name: Upload string result for Job2
      uses: actions/upload-artifact@v1
      with:
        name: stringPrint
        path: stringPrint/string-print.txt
job_3:
  name: Display output
  needs: job_2
  runs-on: macOS-latest
  steps:
    - name: Download output
      uses: actions/download-artifact@v1
      with:
        name: stringPrint

Caching dependencies and build outputs

GitHub recently released the functionality of caching dependencies and build outputs with the aim of improving workflow execution time.

When a job is run, it executes in a clean environment where all the dependencies and required packages are downloaded. This can be a rather time consuming process during development when you deploy your projects. It would be better to avoid the bottleneck of constantly downloading large amounts of data by making use of the caching feature. At first glance, this sounds very similar to the previous section about the persistence of workflow data, and that is exactly true.

The difference is that the use case for caching dependencies differs, it’s intended for files that aren’t altered continuously in jobs making dependencies a good use case. Workflow data persistence is more suitable for data that is continuously altered through multiple jobs in the workflow.

Caching is set up in the YAML file by defining the following:

  • ‘path’, which is a directory to store the cache.
  • ‘key’, which is a unique key for restoring and saving the cache
  • ‘restore keys’: a list of other keys to try if the first key did not result in a hit.

The key is used to identify that specific cache allowing the re-use when a ‘cache-hit’ is identified. If a ‘cache-miss’ is identified the instruction will be ignored. Its model shares resemblance to the CircleCI caching feature using keys and restore_keys/caches.

Johan Abildskov, a DevOps Engineer at Eficode Praqma, wrote an example of caching with Actions which is worth checking out.

Workflow properties

Manual triggering of workflow executions remains absent for now. It is possible through the GitHub API, but is not ideal in terms of operations and developer experiences with CI/CD systems.

This lacking functionality bogs down debugging and can reduce the user experience for developers. After the build is complete, the build output can be downloaded from the GitHub Actions page.

Matrix builds

If your team is building software on more than one supported version of a language, operating system or tool, GitHub Actions allows you to run with those variations through Matrix builds.

Each configuration in the matrix is a copy of the job that runs and reports a status.

In the snippet of code on the next page, I’ve demonstrated a matrix build. In the line ‘runs-on’ I read the values of the list ‘matrix-os’ by parsing over its array which contains a reference to four operating systems (two Ubuntu versions, macOS and Windows).

This means there will be four jobs of `job_1`, each run will be on a different operating system and will produce its own logs and the exact same output given that I stated that in my workflow file.

 

name: Go
on: [push]
jobs:
#
  job_1:
    name: job_1 demonstrates the print of a String
    runs-on: $
    strategy:
      matrix:
        os: [ubuntu-16.04, ubuntu-18.04, macos-latest, windows-latest]

    steps:
      - shell: bash
        run: |
          echo "Hello" > string-print.txt
      - name: Upload stdout results for job 1
        uses: actions/upload-artifact@v1
        with:
          name: stringPrint
          path: string-print.txt

The matrix-builds feature will probably experience heavy use by developers practicing DevOps/NoOps, and the feature will save a lot of time when creating development workflows.

Self-hosted runners

GitHub Actions allows you to run workflows on their servers or on your own servers using the self-hosted runners feature.

Pricing model

Actions is completely free for public repositories. For private repositories, a ‘pay as you go per minute’ model applies. This is more than e.g. CircleCI provides on their free tier.

Conclusion

GitHub Actions has gained widespread attention from the GitHub community. It requires minimal configuration for use: a simple button on your GitHub repositories page triggers the configuration of a CI/CD pipeline. This allows the developer to focus on development rather than setting up the servers for Jenkins, for example, or signing up to other vendors like CircleCI.

There has been little official commentary on functionalities in development, both during beta and after release. This makes it hard to guess about their go-to-market strategy. GitHub’s move to offer CI functionality is a huge deal given the large community that already uses GitHub and a somewhat natural step seeing that competitors like GitLab have had this capability for a while now as automation is gaining more importance.

At the time of writing, Actions was fully released only a couple of days ago and is yet to be industry-proven. While constituting a promising and welcome move for developers, perhaps it’s wise to wait some time before running production workloads.

DEVOPS 2020 will bring together the DevOps community.  Get tickets