exa-Anastasiia
Team Exasol
Team Exasol

A few years ago GitHub introduced its own tool for continuous integration - GitHub Actions. 

We have been using the Travis CI service in the integration team for a while and we still have a lot of our builds running on Travis. But we've also started using GitHub Actions for different tasks.

In this article, I would like to describe how we're using GitHub Actions and why we like them.

 

What are GitHub Actions?

GitHub Actions is a tool provided by GitHub that helps GitHub users to automate routine tasks such as testing, deploying, releasing, and so on. The Actions are available on each GitHub repository by default. It's very convenient: you don't need to create an additional account and connect it to the GitHub repository. If you have never used GitHub Actions before I would recommend checking the official documentation

To start using GitHub Actions, you need to create a `.yml` workflow file where you describe the jobs, steps, and actions you want to use. Workflows belong to a repository: each repository has its own workflows. Actions reside in the workflow files.

 

How to run GitHub Actions?

When we tell 'Run GitHub Actions' we mean 'Run GitHub workflow', because actions are always a part of a workflow. To run a workflow, you need to trigger a workflow. There are three main ways to do it. You specify it while creating a workflow file:

  1. It can be triggered on an event (push, pull request, etc);
  2. You can schedule a run;
  3. You can start a run manually at any time via GitHub GUI or via GitHub Actions API;

After you've added a workflow file to the default branch of your repository, you can find available workflows in the `Actions` tab of the repository.

 

Why do we like GitHub Actions?

We like GitHub Actions and here are a few reasons why.

  • GitHub Actions are easy to integrate into a repository: you only need to create a workflow file and put it into the `.github/workflows` directory of a repository. GitHub will recognize workflow files and add them to the project automatically.
  • GitHub Actions provide a lot of officially supported and also third-party actions. You can also create your own action. 
  • Each workflow has an artifactory where you can store some data. This can be useful if your actions produce the output you want to use later.
  • GitHub provides API for the Actions. The API covers a wide range of the Actions' features and allows us to control the Actions remotely, via code, for example.

 

What kind of problems do we solve with GitHub Actions?

I would like to show you a few examples of how we're using GitHub Actions in our projects. We will take a look at three examples of workflow files and I'll describe in detail how to read those files.

 

Example 1. We check the dependencies of our projects every night to discover freshly reported vulnerabilities.

We want to know as soon as possible if there are some vulnerabilities in the dependencies we use. Even if there are no known vulnerabilities today, there is no guarantee that tomorrow new vulnerabilities won't appear. That's why we run nightly checks using GitHub Actions.

Here is a `dependendecies_check.yml` file that resides in the `.github/workflows` directory of each project we want to check:

name: Dependencies Check

on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Check out the repository
        uses: actions/checkout@v2

      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Checking dependencies for vulnerabilities
        run: mvn org.sonatype.ossindex.maven:ossindex-maven-plugin:audit -f pom.xml

Let's read the file line by line now. 

A name of a workflow is an optional parameter. The name is displayed in the workflow list of the repository. If you don't specify the name, the file name will be displayed instead.

name: Dependencies Check

`on` is a required parameter. It specifies the events that trigger a workflow. In this case, we run it on schedule: every day at 02:00 AM.

on:
  schedule:
    - cron: "0 2 * * *"

Jobs are processes that can run in parallel or sequentially. We have only one job with id `build`. We must specify the machine type to run our job on. `runs-on` is a mandatory parameter and we set it to `ubuntu-latest`.

 
jobs:
  build:
    runs-on: ubuntu-latest

A job consists of `steps` that are basically tasks. Our first `step` - check out the repository we run the workflow on. We use an action for it. A `name `is just for us to see the step name in the logs. And a `uses` defines which action we use.

An action is a small code library that somebody created to perform a certain task. The `actions/checkout` is an action created by GitHub, you can also check the repository with this action. Anybody can create an action and publish it, so you don't need to re-invent actions for common tasks, there are a lot of ready-to-be-used actions. 

steps:
    - name: Check out the repository
      uses: actions/checkout@v2

Our next step - install JDK 11 as our project is written in Java. We use another official action from GitHub for it. This action allows us to specify a version of JDK, so we set it to 11.

- name: Set up JDK 11
      uses: actions/setup-java@v1
      with:
        java-version: 11

The last and the most important step: we check the dependencies. For that, we use the `run` parameter which executes our command using the operating system's shell. The command itself is a maven command executing a plugin that runs the dependencies validation on the pom.xml file of the project.

- name: Checking dependencies for vulnerabilities
  run: mvn org.sonatype.ossindex.maven:ossindex-maven-plugin:audit -f pom.xml

If the maven plugin validation fails, the whole workflow fails and we got an e-mail notification.

 

We have a lot of external links in our documentation and we want to keep them up to date. So here is the next GitHub workflow:

name: Broken Links Checker

on:
  schedule:
    - cron: "0 5 * * *"
  push:

jobs:
  linkChecker:
    runs-on: ubuntu-latest

    steps:
      - name: Check out the repository
        uses: actions/checkout@v2

      - name: lychee Link Checker
        id: lc
        uses: lycheeverse/lychee-action@v1.0.7

      - name: Fail if there were link errors
        run: exit ${{ steps.lc.outputs.exit_code }}

It has a lot in common with the previous workflow, so I don't want to describe it again. Here are just a few interesting details;

  • This workflow runs both on schedule at 05:00 AM every day and on push to any branch.
  • This workflow uses a third-party action Lychee Broken Link Checker.
  • It fails if there were some unreachable links during the run.

 

Example 3. We create a checksum of a repository and store it to use later

This workflow is a part of our automated release project. We want to calculate a checksum of the packaged project files and store the checksum in the workflow artifactory.

name: Prepare Checksum

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout the repository
        uses: actions/checkout@v2

      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Run tests and build with Maven
        run: mvn -B clean verify --file pom.xml

      - name: Prepare checksum
        run: find target -maxdepth 1 -name *.jar -exec sha256sum "{}" + > original_checksum

      - name: Upload checksum to the artifactory
        uses: actions/upload-artifact@v2
        with:
          name: original_checksum
          retention-days: 5
          path: original_checksum

This workflow only runs if manually triggered. You can trigger a workflow using GitHub GUI or Actions API.

on:
  workflow_dispatch:

We build the project with Maven and create an `original_checksum` file that stores a sha256sum checksum of each generated JAR file.

- name: Run tests and build with Maven
   run: mvn -B clean verify --file pom.xml

- name: Prepare checksum
  run: find target -maxdepth 1 -name *.jar -exec sha256sum "{}" + > original_checksum

We upload the checksum file to the workflow artifactory and store it there for 5 days. After 5 days it will be removed automatically. The stored file is accessible via GUI and API.

- name: Upload checksum to the artifactory
  uses: actions/upload-artifact@v2
  with:
    name: original_checksum
    retention-days: 5
    path: original_checksum

 

What could be better?

GitHub Actions is a relatively young tool and it's still actively developed. At the moment of writing this post (April 2021) there are a few missing features that would be really nice to have:

  • It's not possible to share a workflow file between multiple repositories.  You need to duplicate the `.yml` file to each repository you want to use it on.
  • A new workflow only appears in the `Actions` tab when the file is on the default branch. It's hard to test your workflow file if it's not merged yet, this especially affects workflow with `workflow_dispatch` triggering.
  • When triggered manually, a workflow can accept arguments, but it doesn't provide a secure way to accept credentials. You can set the credentials via GitHub Secrets though.

 

Conclusion

GitHub Actions is a powerful and user-friendly tool that helps GitHub users automate different routine tasks. The Actions are well documented and the GitHub Community is constantly adding new Actions. If you have not used the Actions I would recommend giving them a try.