Red pipes
Photo by Jan van der Wolf from Pexels

Migrating to GitHub Actions

Brett Uglow
DigIO Australia

--

GitHub Actions (GHA) is awesome, free, and very easy to get working. I’m going to walk you through how to convert a GitHub repo from using Travis CI to using GitHub Actions.

Motivation

Throughout my open-source career, I’ve relied on Travis CI to provide continuous integration services (test changes, produce builds, release). Travis CI used to be free for open source projects — and it still is — but Travis CI is no longer a great place for open source software (you can read Jeff Geerling’s excellent summary). After Travis CI rolled out their changes, I often found delays of 30+mins before a build agent was assigned to my task. That’s far too long to be useable. It is time to switch!

GitHub Actions concepts

Before diving in, there are just a few concepts to bear in mind with GHA (and the docs are pretty good, so check them out later).

  • Actions are defined with workflow files using YAML syntax.
  • Workflows consist of jobs. Jobs consist of steps. (If you’ve used Travis CI or GitLab CI before, this is all very similar).
  • A job runs-on a virtual machine (VM), which can be hosted by GitHub (easy) or self-hosted (less-easy). Jobs run in parallel by default, but can be changed to run sequentially when required.
  • Steps define the commands you wish to run, or the other actions you wish to use. This is a key feature which allows you (or third parties) to create your own actions for re-use across other repos.

Setup Guide

This guide will show the steps to convert a Node package repo to use GHA. However the particular source-code language used doesn’t matter — the steps for setting up actions are the same. (Though deployment-steps will vary based on where the software is distributed.)

Step 1 — Click on the Actions tab

The Actions tab on your GitHub repo

Step 2 — Choose a template Workflow (or create your own)

Before you have any actions, the Actions page looks like this:

The New Actions page

There are two options at this point:

  1. Create your own workflow, or…
  2. Use a template workflow.

I would suggest using a template initially, just to get familiar with the syntax. However, I’m going to use a workflow that I’ve prepared earlier 😇.

Step 3 — Create your workflow file

If you use a template, GHA will do this step for you, so you can skip to the next step. Otherwise, read on…

In your project’s repo, create a file called .github/workflows/publish.yml. The .github and workflows folders are significant. But the file name can be any valid YAML file name.

Then paste this config into your file:

# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: Node.js Packageon:
pull_request:
push:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
# Run the steps below with the following versions of Node.js
node-version: [ 14.x, 16.x ]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: npm ci
- name: Test
run: npm run test:coverage
- name: Verify
run: npm run verify
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./test-reports/coverage/lcov.info
parallel: true
test-finished:
needs: [ test ]
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
release:
# Only release on push to master
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
# Waits for test jobs for each Node.js version to complete
needs: [ test ]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14.x
- name: Install
run: npm ci
- name: Release
run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Ok, let me break this down… (skip to the next section if you understand this already)

# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: Node.js Test & Semantic Releaseon:
pull_request:
push:
branches: [ master ]

This section defines the name of the workflow, and the events that trigger the workflow. In this example, there are 2 triggers; when pull-requests are raised/changed, and then code is pushed to the master branch.

In Travis CI, the triggers are defined in their Admin console, rather than in the YAML. The GHA approach is a lot more flexible, and there many more events that can trigger a workflow than just code-commits!

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
# Run the steps below with the following versions of Node.js
node-version: [ 14.x, 16.x ]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: npm ci
- name: Test
run: npm run test:coverage
- name: Lint
run: npm run lint

This section defines the jobs within this workflow. There are three jobs, and the one above is the test job. As the name implies, it is responsible for testing the code. It uses the ubuntu-latest GitHub-hosted VM to execute the code.

The strategy section defines a build-matrix — a way of running the job’s steps against multiple software configurations. In the section above, the steps will be executed on 2 different NodeJS versions. This isn’t a required approach for every repo, but it shows what is possible.

Next, the steps section lists the steps required to test the software. This is the same as TravisCI’s install and script sections.

      - name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./test-reports/coverage/lcov.info
parallel: true
test-finished:
needs: [ test ]
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true

This section is only required for the Coveralls code-coverage platform (free for open source projects 😄).

The first section (above) defines a code-coverage step, using Coverall’s published GitHub action. We have to tell it where to find the coverage report (path-to-lcov) and that a build matrix is in use (parallel).

The next section is a job that uses needs to tell GHA not to start this job until the test job has completed (which means it won’t start until all of the build-matrix jobs in test have completed). Note that runs-on is specified for each job, which means you could use a different VM here if you wished.

The last section is an NPM publishing job. An interesting part of this job is the if clause:

release:
# Only release on push to master
if: github.event_name == 'push' && github.ref == 'refs/heads/master'

While the workflow can be triggered by pull-requests AND changes-to-the-main branch, this job is restricted to run only when code is pushed to the main branch (master).

Step 4 — Committing the file & push to GitHub

If you are using the workflow template then GitHub will allow you to commit the file from within the website directly to the main branch, or to a new branch (do this, please).

Otherwise, create a branch, commit your changes and push it to GitHub. Assuming that this is your first time using GHA, you are likely to receive a message like this:

refusing to allow an OAuth App to create or update workflow .github/workflows/cd.yml without workflow scope"

Thanks to the power of the internet (Stack Overflow), there are some solutions depending on your operating system. I’m using OSX, so this response worked for me.

Step 5— Create a pull request (PR)

Go to your repo, select the Pull requests tab and you should see an option to create a new PR based on the branch that you just pushed. Press the shiny green button.

Then press the subsequent green button to create the PR. This is where the magic happens.

Step 6— See the action running!

Your PR page will start to update as GHA sends status-updates to the page regarding the progress of jobs in your workflow:

… and then…

🎉🎉🎉🎉 !!! Party Time !!! 🎉🎉🎉🎉

Note that the skipped-job is clearly identified and correctly skipped for pull requests!

So that’s how you can migrate to GitHub Actions from Travis CI!

If you’d like to know about secrets.GITHUB_TOKEN and semantic-releasing, keep reading…

Bonus — Setting up secrets (for semantic releasing!)

In the workflow file (even if you used a template), you may see a reference to secrets.GITHUB_TOKEN. What is this? GHA has a secrets manager for each repo, to allow you to store secrets! Go to your repo > Settings > Secrets.

This is what you will see:

Nothing… nothing!

Yes, you will see nothing. No secrets. But what about the GITHUB_TOKEN secret? Ahh, well, that’s a special secret that GitHub deliberately does not show here.

So let’s setup an actual secret in the course of setting up a semantic-releasing workflow.

Why you should be using a Semantic Release workflow

This could easily be a blog post in and of itself, but the short answer is:

You have more important things to worry about than choosing version numbers for software releases. There are tools that can do this for you. Use them.

The semantic-release toolchain is one such tool which works well for NodeJS packages. So let’s look at an example of setting up a GitHub secret while also setting up semantic-releasing.

Step 1 — Do the semantic-release quiz

According to the docs, the easiest way to set this up is to open a terminal window and type:

cd <your repo>
npx semantic-release-cli setup

Note: This command presumes you have NodeJS installed.

You will then be prompted for your NPM credentials and a GitHub personal access token:

? What is your npm registry? https://registry.npmjs.org/
? What is your npm username? u_glow
? What is your npm password? [hidden]
? Provide a GitHub Personal Access Token (create a token at https://github.com/settings/tokens/new?scopes=repo) ghp_xxxx...
? What CI are you using? Github Actions

…and that’s it!

Step 2 — View your secret

If you now refresh your GitHub repo > Settings > Secrets page, you should see this:

There’s a secret now!

The semantic-release tool used your GitHub personal access token to create the NPM_TOKEN for your repo. Now you can semantically & automatically release your NodeJS software whenever code is merged to master via Github Actions instead of Travis CI!

Note: I’ve left out much of the details of setting up semantic-releasing to focus on the GHA secret generation step. Please read the docs to learn more about the configurations you can use, or refer to one of my recently-updated repos to see the configuration I use.

One thing to note…

A common thing you may want to do when using semantic-release in a GitHub workflow is to notify other workflows of the release. However, if you use the standard GITHUB_TOKEN (as in this workflow-example below), no other workflows will be notified of the release!

jobs:
release:
...
steps:
...
- name: Release
id: semantic
run: npx semantic-release
env:
# secrets.GITHUB_TOKEN cannot notify other workflows!
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Fixing this requires the creation of a Personal Access Token:

  1. Create a token by going to your GitHub profile > Settings > Developer Settings > Personal Access Tokens > Generate New Token.
  2. Give the token a descriptive name and the repo & workflow scope, generate it, then copy the value to the clipboard.
  3. Go back to your repo, then Settings > Secrets > New Repository Secret
  4. Give the secret a name like MY_REPO_WORKFLOW_TOKEN, and paste the token as the secret-value. Save the secret.
  5. Change your one line in your GitHub workflow to GITHUB_TOKEN: ${{ secrets.MY_REPO_WORKFLOW_TOKEN .

Now, when the semantic-release workflow decides that a release is necessary, another workflow can be triggered to do something. For example, this workflow creates a Docker build using the version number of the release:

# See: https://github.com/marketplace/actions/push-to-ghcrname: Build and publish a Docker image to ghcr.io
on:
release:
types: [ published ]

jobs:
docker_publish:
runs-on: "ubuntu-20.04"
steps:
- uses: actions/checkout@v2
- name: Build and publish Docker image
uses: macbre/push-to-ghcr@master
with:
image_name: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}

--

--

Brett Uglow
DigIO Australia

Life = Faith + Family + Others + Work + Fun, Work = Software engineering + UX + teamwork + learning, Fun = travelling + hiking + games + …