Manually Triggering GitHub Workflows

Manually Triggering GitHub Workflows

Oct 11, 2019

Introduction

Most workflows designed with GitHub Actions react to GitHub webhook events such as pull_request, push, etc. However, there are times when a workflow just needs to be triggered via human intervention.

Consider the following example: a company currently migrating from Jenkins to GitHub Actions needs a workflow with which to rollback any given microservice to a specific version when shit hits the fan due to some corner cases not being covered by the test suite. How can we achieve this task using GitHub Actions?

The repository_dispatch event

What is it

Out of all the possible webhook events in the documentation, repository_dispatch is the one we’re after. When any given workflow is designed to react to this event, a webhook is configured for that repository so external events can trigger this workflow using the GitHub API.

Example Workflow

Following with the previous example about the on-demand rollback workflow, here’s a high level YAML file that might do just that:

name: On-Demand Rollback

on: repository_dispatch

jobs:
  rollback:
    runs-on: ubuntu-latest
    name: Rollback a microservice to a specific version
    steps:
      - env:
          MICROSERVICE: "auth"
          VERSION: "2.13.1"
        # Assumes `rollback` logic is implemented
        run: rollback $MICROSERVICE $VERSION

Triggering the workflow

Before proceeding any further, create a personal token with full repo permissions first, since it’s needed in order to be able to send events to the repository_dispatch webhook.

curl -X POST \
    -H "Authorization: token ${GITHUB_TOKEN}" \
    -H 'Accept: application/vnd.github.everest-preview+json' \
    -d '{"event_type":"rollback"}' \
    https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO}/dispatches

Limitations

We can now trigger the job on-demand, that’s right. Yet, it’s going to get cumbersome if every time we need to rollback we need to commit a change to this file. We need a way to parametrize this job so it can rollback any service to any version.

Adding a payload

Hacking the event_type input format

The first option that comes to mind is that the event_type parameter (of type string) from the above cURL call could be set to a particular value, then referenced in the workflow YAML file. Therefore, values like auth/2.13.1 could make a job run rollback the auth microservice to version 2.13.1.

name: On-Demand Rollback

on: repository_dispatch

jobs:
  rollback:
    runs-on: ubuntu-latest
    name: Rollback a microservice to a specific version
    steps:
      - run: |
          microservice=$(echo "${{ toJson(github.event.action) }}" | cut -f 1 -d "/")
          version=$(echo "${{ toJson(github.event.action) }}" | cut -f 2 -d "/")
          rollback $microservice $version

And the corresponding curl call:

curl -X POST \
    -H "Authorization: token ${GITHUB_TOKEN}" \
    -H 'Accept: application/vnd.github.everest-preview+json' \
    -d '{"event_type":"auth/2.13.1"}' \
    https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO}/dispatches

For now this could work. However as soon as we needed to input more data to this workflow things would get nasty quite fast. There must be a better approach.

Leveraging client_payload

Turns out there is an undocumented parameter to the repository_dispatch event called client_payload which is not of type string but can be an arbitrary JSON 🎉

You can then reference any value in this JSON like you would reference any other context value in a workflow definition.

name: On-Demand Rollback

on: repository_dispatch

jobs:
  rollback:
    runs-on: ubuntu-latest
    name: Rollback a microservice to a specific version
    steps:
      - run: |
          rollback "${{ toJson(github.event.client_payload.microservice) }}" \
                   "${{ toJson(github.event.client_payload.version) }}"

Here’s how a curl call can pass all of this information to the repository_dispatch webhook:

curl -X POST \
    -H "Authorization: token ${GITHUB_TOKEN}" \
    -H 'Accept: application/vnd.github.everest-preview+json' \
    -d '{"event_type":"rollback","client_payload":{"microservice":"auth","version":"2.13.1"}}' \
    https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO}/dispatches

More possibilities

Executing a curl command every time is tiring and error-prone. I thought an interesting possibility would be to create a static website whereby the user can fill a form, then click a button thus triggering an XMLHttpRequest in the background that executes the job.