GitHub Actions workshop

This hands-on workshop provides a introduction to building pipeline workflows using GitHub Actions and creating your first GitHub Action using JavaScript, TypeScript or Docker.

Welcome

Welcome all to the full-day workshop on GitHub Actions and Packages

Agenda

Prerequisites

:warning: Make sure to configure two-factor-authentication for your GitHub account.

At the end of each labs you will find links to relevant resources.

Tip: follow the if applied, this commit will <your subject line here> best practice for every commit message so you know what is being tested by the build run. For example:

Add build and test workflow

Resources


Lab 1: Create repository from template

Create a new repository from octocat-zen:

Resources


Lab 2: Add starter workflow

Let’s get started with GitHub Actions:

Note: GitHub Actions are free for public repositories and on a free plan you get 2000 build minutes.

Bonus: make a change in a new pull request that breaks the build.

For example you could add an array test in test.js:

var assert = require('assert');

[...]

describe('Array', function () {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      // replace 4 with value that is present in the array
      assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});

Resources


Lab 3: Add a linter step

In this lab we remove the matrix build and add a linter step:

strategy:
  matrix:
  node-version: [8.x, 10.x, 12.x]
- name: Run linter
  run: |
    npm install eslint --save-dev
    ./node_modules/.bin/eslint --no-eslintrc .

Note On GitHub Marketplace you can also find various actions for ESLint and other code quality tools.

Resources


Lab 4: Define a job level environment variable

You can use environment variables at the step and job level:

env:
  NODE_VERSION: 12.x

Resources


Lab 5: Dump contexts

In this lab we add another workflow to take a look at the workflow context. We will also look at a few functions:

name: Dump contexts

on: push

jobs:
  echo:
    runs-on: ubuntu-16.04
    steps:
      - name: Dump GitHub context
        env:
          GITHUB_CONTEXT: $
        run: echo "$GITHUB_CONTEXT"
      - name: Dump job context
        env:
          JOB_CONTEXT: $
        run: echo "$JOB_CONTEXT"
      - name: Dump steps context
        env:
          STEPS_CONTEXT: $
        run: echo "$STEPS_CONTEXT"
      - name: Dump runner context
        env:
          RUNNER_CONTEXT: $
        run: echo "$RUNNER_CONTEXT"
- name: Dump variables
  env:
    EVENT_NAME: $
    REF: $
  run: echo "Event name $EVENT_NAME and ref $REF"
format('Hello {0} {1} {2} {3}', 'Hubot', 'the', 'friendly', 'robot')

- name: Outputs 
  env:
    EVENT_NAME: ${{ github.event_name }}
    REF: ${{ github.ref }}
    ROBOT: ${{ format('Hello {0} {1} {2} {3}', 'Hubot', 'the', 'friendly', 'robot') }}
  run: |
    echo "Event name $EVENT_NAME and ref $REF"
    echo "$ROBOT"

You can process the variables like any other environment variable, for example to replace Hubot with Probot:

echo "${ROBOT/Hubot/Probot}"

Resources


Lab 6: Add a comment to the pull request

Let’s add a comment to thank the developer for opening the pull request. We will use an if statement to check if the pull request is opened as we only want to add the comment once.

You can add if statements both on the step and job level. You can also use or operators (||):

if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')

And and operators (&&):

if: github.event_name == 'pull_request' && github.event.action == 'closed'

And combine operators with functions:

if:  github.event.action == 'opened' && ( startsWith(github.event.issue.title, 'demo') )
- name: Add comment 
  if: github.event_name == 'pull_request' && (github.event.action == 'opened')
  uses: actions/github@v1.0.0
    env:
      GITHUB_TOKEN: $
    with:
      args: comment "Thank you for opening this pull request :tada::sparkles:"

Resources


Lab 7: Add caching for node_modules

Actions allow you to cache dependencies and build outputs in GitHub Actions:

- uses: actions/cache@v1
  with:
    path: ~/.npm
    key: $-node-$
    restore-keys: |
      $-node-
- name: Cache dependencies

You can also upload and download build artifacts, for example if you want to use build artifacts across different jobs in a workflow:

- name: Upload report
  uses: actions/upload-artifact@master
    with:
      name: coverage
      path: $/coverage

Resources


Lab 8: Deploy to Zeit

It is time to deploy our code. For this workshop we use Zeit as deployment target:

- name: Deploy to Zeit
  uses: amondnet/now-deployment@v1
    with:
      github-token: $
      zeit-token: $

Bonus: Update the workflow to only run when the branch is master or update to run the deployment step only when the branch is master. After this task, remove any branch filters and commit.

Resources


Lab 9: Add the deployment to the pull request

With the Deployment API you can report a deployment status in the pull request:

- uses: octokit/request-action@v1.x
  id : create_deployment
  with:
    route: POST /repos/:owner/:repo/deployments
    ref: $
    required_contexts: "[]"
    environment: "review"
  env:
    GITHUB_TOKEN: $
- uses: gr2m/get-json-paths-action@v1.x
  id: parse_deployment
  with:
    json: $
    id: "id"
- uses: octokit/request-action@v1.x
  with:
    route: POST /repos/:owner/:repo/deployments/:deployment_id/statuses
    deployment_id: $
    environment: "review"
    state: "success"
    target_url: $
  env:
    GITHUB_TOKEN: $

- uses: octokit/request-action@v1.x
  if: failure()
  with:
    route: POST /repos/:owner/:repo/deployments/:deployment_id/statuses
    deployment_id: $
    environment: "review"
    state: "failure"
  env:
    GITHUB_TOKEN: $

Resources


Lab 10: Publish mpn package

In this lab we will publish our project as npm package to GitHub Package Registry.

Note: make sure to add your handle to the scope.

publish-gpr:
  needs: build
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v1
    - uses: actions/setup-node@v1
      with:
        node-version: 12.x
        registry-url: https://npm.pkg.github.com/
        scope: '@<your handle>'
    - run: npm ci
    - run: npm publish
      env:
        NODE_AUTH_TOKEN: $

Resources


Lab 11: Add a badge

Let’s add a badge to the README:

![](https://github.com/<owner>/<repository>/workflows/Node%20CI/badge.svg)

Resources


Lab 12: Publish Docker container

GitHub Package Registry can also be used to publish Docker images.

Create a new job publish-docker that builds and tags the image and then publishes the tagged image to GitHub Package Registry:

Note: make sure to update the tag names to reflect your handle and repository name.

publish-docker:
  needs: [build]
  runs-on: [ubuntu-latest]
  steps:
  - uses: actions/checkout@master
  - run: |
      docker build -t docker.pkg.github.com/<your handle/octocat-zen/octocat-zen:$ .
      docker login docker.pkg.github.com -u <your handle> -p $GITHUB_TOKEN
      docker push docker.pkg.github.com/<your handle>/octocat-zen/octocat-zen:$
    env:
      GITHUB_TOKEN: $

Resources


Lab 13: Create release

For a JavaSCript project you can use semantic-release to automatically create releases. This is a bit harder to test as it will closely examine your commits to judge if it justifies a release.

on:
  push:
    branches:
      - master
      - next

name: Release
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
      - uses: actions/setup-node@v1
        with:
          node-version: "12.x"
      - run: npx semantic-release -r git@github.com:<your handle>/octocat-zen.git
        env:
          GITHUB_TOKEN: $
          NPM_TOKEN: $

Resources


Lab 14: Use the issue event

In the following example we will use the issue event to add a comment to an issue when it is created. We use actions/github to comment on the issue when it is opened.

name: GitHub Zen

on:
  issues:
    types: [opened]

jobs:
  build:
    runs-on: ubuntu-latest   
  
    steps:  
  
    - id: zen
      run: echo ::set-output name=quote::$(curl https://api.github.com/zen)

    - name: GitHub Zen comment
      uses: actions/github@v1.0.0
      env:
        GITHUB_TOKEN: $
      with:
        args: comment ">$ :heart::octocat:"
- name: GitHub zen comment
  uses: octokit/request-action@v1.x
    with:
      route: POST /repos/:owner/:repo/issues/:issue_number/comments
      issue_number: $
      body: '">$ :heart::octocat:"'
    env:
      GITHUB_TOKEN: $

Resources


Lab 15: Creating a JavaScript action

Follow the tutorial Creating a JavaScript action to create your first JavaScript action.

You can also use Docker to create actions.

Resources


Lab 16: Create your own action

If you want to interact with GitHub you can install and import the @actions/github library and create a client:

import * as core from '@actions/core';
import * as github from '@actions/github';
import * as yaml from 'js-yaml';

async function run() {
  try {
    const ref = core.getInput('ref', { required: true });
    const task = core.getInput('task');
    const autoMerge: boolean = yaml.load(core.getInput('auto_merge'));

    const client = new github.GitHub(token);

    core.debug('Returning the ref value');

    core.setOutput('ref', ref)

  } catch (error) {
    core.setFailed(error.message);
  }
}

Scheduled events and dispatcher event

Scheduled events

GitHub Actions also supports scheduled events. See actions/stale for an example.

Dispatch event

You can use the GitHub API to trigger a webhook event called repository_dispatch when you want to trigger a workflow for activity that happens outside of GitHub.

Resources


Appendix 1: the complete nodejs.yml workflow

The following workflow is an example of the completed nodejs.yml workflow:

Note: if you want to use this workflow please updated the owner value to match your handle and make sure you use your repository path and name.

name: Node CI

on: [pull_request]

jobs:
  build:
    env:
      NODE_VERSION: 12.x
    
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    
    - name: Add comment 
      if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
      uses: actions/github@v1.0.0
      env:
        GITHUB_TOKEN: $
      with:
        args: comment "Thank you for opening this pull request :tada::sparkles:"
      
    - name: Cache dependencies
      uses: actions/cache@v1
      with:
        path: ~/.npm
        key: $-node-$
        restore-keys: |
          $-node-
    
    - name: Use Node.js $
      uses: actions/setup-node@v1
      with:
        node-version: $
    
    - name: npm install, build, and test
      run: |
        npm ci
        npm run build --if-present
        npm test
      env:
        CI: true

    - name: Run linter
      run: |
        npm install eslint --save-dev
        ./node_modules/.bin/eslint --no-eslintrc .
    
    - uses: octokit/request-action@v1.x
      id: create_deployment
      with:
        route: POST /repos/:owner/:repo/deployments
        ref: $
        required_contexts: "[]"
        environment: "review"
      env:
        GITHUB_TOKEN: $

    - uses: gr2m/get-json-paths-action@v1.x
      id: parse_deployment
      with:
        json: $
        id: "id"

    - name: Deploy to Zeit
      uses: amondnet/now-deployment@v1
      id: zeit_deployment
      with:
        github-token: $
        zeit-token: $

    - uses: octokit/request-action@v1.x
      with:
        route: POST /repos/:owner/:repo/deployments/:deployment_id/statuses
        deployment_id: $
        environment: "review"
        state: "success"
        target_url: $
      env:
        GITHUB_TOKEN: $

    - uses: octokit/request-action@v1.x
      if: failure()
      with:
        route: POST /repos/:owner/:repo/deployments/:deployment_id/statuses
        deployment_id: $
        environment: "review"
        state: "failure"
      env:
        GITHUB_TOKEN: $

  publish-gpr:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
          registry-url: https://npm.pkg.github.com/
          scope: '@<your handle>s'
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: $
                   
  publish-docker:
    needs: [build]
    runs-on: [ubuntu-latest]
    steps:
    - uses: actions/checkout@master
    - run: |
          docker build -t docker.pkg.github.com/<yourhandle>/octocat-zen/octocat-zen:$ .
          docker login docker.pkg.github.com -u <your handle> -p $GITHUB_TOKEN
          docker push docker.pkg.github.com/<your handle>/octocat-zen/octocat-zen:$
      env:
        GITHUB_TOKEN: $

The number of community GitHub Actions is fast growing. Here are some actions for commonly used tools and services:

Cloud providers

See also