/dispatch-workflow

A GitHub Action to Dispatch and Discover GitHub Workflows using workflow_dispatch or repository_dispatch

Primary LanguageTypeScriptMIT LicenseMIT

GitHub Action for Dispatching Workflows

A universal action that supports dispatching workflows with either the workflow_dispatch or repository_dispatch event. Additionally, this action can be configured to discover the Run ID of a dispatched workflow through a efficient and accurate correlation algorithm.

The latter algorithm was designed as a workaround for a technical limitation that prevents the dispatch APIs from returning a Run ID.

There was a need for this action as currently available actions...

  • Support the workflow_dispatch or repository_dispatch event, but not both
  • Use Run ID extraction algorithms that are either API-intensive or unreliable on repositories that experience a high velocity of workflows

Acknowledgements

This GitHub Action is a fork of codex-/return-dispatch. This action supported the ability to extract a Run ID, but exclusively supported the workflow_dispatch method. I decided to fork this action as it had an intuitive code-base and excellent testing philosophy.

From a compatibility and performance perspective, this GitHub Action superseedes codex-/return-dispatch, as it additionally supports the repository_dispatch method and uses a more efficient algorithm to extract the Run ID for a dispatched workflow

Usage

Creating Dispatch Events

workflow_dispatch

steps:
  - uses: lasith-kg/dispatch-workflow@v1
    id: workflow-dispatch
    name: 'Dispatch Workflow using workflow_dispatch Method'
    with:
      dispatch-method: workflow_dispatch
      repo: repository-name
      owner: repository-owner
      ref: refs/heads/main # or main
      workflow: automation-test.yml # Or Workflow ID
      token: ${{ secrets.TOKEN }} # GitHub Token With Relevant Permissions
      workflow-inputs: |
        {
          "string-type": "placeholder",
          "number-type": "1",           // Workaround for 'Number' types
          "boolean-type": "true"        // Workaround for 'Boolean' types
        }

repository_dispatch

steps:
  - uses: lasith-kg/dispatch-workflow@v1
    id: repository-dispatch
    name: 'Dispatch Workflow using repository_dispatch Method'
    with:
      dispatch-method: repository_dispatch
      repo: repository-name
      owner: repository-owner
      event-type: deploy # Event To Trigger From Workflow
      token: ${{ secrets.TOKEN }} # GitHub Token With Relevant Permissions
      workflow-inputs: |
        {
          "string-type": "placeholder",
          "nested": {                    // Supports Nesting
            "number-type": 1,            // Supports Native 'Number' types
            "boolean-type": true         // Supports Native 'Boolean' types
          }
        }

Receiving Dispatch Events

workflow_dispatch

# .github/workflows/automation-test.yml [refs/heads/main]
name: Workflow Name
on:
  workflow_dispatch:
    inputs:
      string-type:
        description: An input of type 'String'
        required: true
        type: string
      number-type:
        description: An input of type 'Number'
        type: number
      boolean-type:
        description: An input of type 'Boolean'
        type: boolean

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Echo Inputs
        run: |
          echo "${{ inputs.string-type }}" # "placeholder"
          if [[ "${{ inputs.number-type }}" -gt -1 ]]; then echo "🟢"; fi       # 🟢
          if [[ "${{ inputs.boolean-type }}" == "true" ]]; then echo "🟢"; fi   # 🟢

repository_dispatch

name: Workflow Name
on:
  repository_dispatch:
    types:
      - deploy

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Echo Inputs
        run: |
          echo "${{ github.event.client_payload.string-type }}" # "placeholder"
          if [[ "${{ github.event.client_payload.nested.number-type }}" -gt -1 ]]; then echo "🟢"; fi      # 🟢
          if [[ "${{ github.event.client_payload.nested.boolean-type }}" == "true" ]]; then echo "🟢"; fi  # 🟢

Discovery

One of the drawbacks with both dispatch methods, is that they do not natively return a Run ID that allows us to query for the status of our dispatched workflow. This technical limitation is discussed more in-depth in this community discussion. We can work around this by encorporating a Distinct ID into our dispatch event. We then have the ability to discover the dispatched workflow, from all workflow runs, by correlating it to the Distinct ID.

This functionality is disabled by default, but can be enabled with the discover: true configuration. The receiving workflow must then be modified appropriated to intercept the Distinct ID.

Creating Dispatch Events with Discovery

steps:
  - uses: lasith-kg/dispatch-workflow@v1
    id: dispatch-with-discovery
    name: "Dispatch Workflow With Discovery"
    with:
      ...
      discover: true
  - id: echo-run-id-url
    name: "Echo Run ID and Run URL"
    run: |
      echo "${{ steps.dispatch-with-discovery.outputs.run-id }}"
      echo "${{ steps.dispatch-with-discovery.outputs.run-url }}"

Receiving Events with Discovery

On September 26, 2022, GitHub introduced the ability to set dynamic names for workflow runs. The new run-name attribute will accept expressions, thus allowing us to inject the Distinct ID into the queryable view.

The expression to expose the Distinct ID in the run-name depends on what dispatch method you are using. The included expressions have been configured in a way to return a placeholder value N/A if a Distinct ID is not available.

workflow_dispatch

name: Workflow Name
run-name: Workflow Name [${{ inputs.distinct_id && inputs.distinct_id || 'N/A' }}]

on:
  workflow_dispatch:
    inputs:
      distinct_id:
        description: 'Distinct ID'
        required: false

repository_dispatch

name: Workflow Name
run-name: >
  Workflow Name [${{
    github.event.client_payload.distinct_id &&
    github.event.client_payload.distinct_id || 'N/A' }}]

on:
  repository_dispatch:
    types:
      - deploy

Permissions

Dispatching a Workflow requires an authenticated GITHUB_TOKEN. The required permissions for this GITHUB_TOKEN depends on the following factors...

  • Dispatch Method: repository_dispatch, workflow_dispatch
  • Discovery: true, false
  • Repository Visiblity: Private, Public

Generating a GITHUB_TOKEN

There are also multiple methods of generating GITHUB_TOKEN. If you are dispatching a workflow from the current repository, a GitHub Actions Token would be the most secure option. If you are dispatching a workflow to a remote repository, I would personally recommend a GitHub App Token. GitHub App Tokens are ephemeral (valid for 1 hour) and have fine grained access control over permissions and repositories. Additionally they are not bound to a particular developers identity, unlike a Personal Access Token.

The below table shows the neccessary permissions for all the unique combinations of these factors. If using a Fine Grained Token, ensure that the permissions correspond to the repository that contains the workflow you are attempting to dispatch.

Mode Fine Grained Tokens Personal Access Token (Classic)
repository_dispatch contents: write Private: repo / Public: public_repo
repository_dispatch + discover: true contents: write + actions: read Private: repo / Public: public_repo
worflow_dispatch actions: write Private: repo / Public: public_repo
workflow_dispatch + discover: true actions: write Private: repo / Public: public_repo

Inputs

Name Description Required Default
dispatch-method The method that will be used for dispatching GitHub workflows: repository_dispatch, workflow_dispatch true
repo Repository of the workflow to dispatch true
owner Owner of the given repository true
token GitHub API token for making API requests true
ref If the selected dispatch method is workflow_dispatch, the git reference for the workflow. The reference can be a branch or tag name conditional
workflow If the selected dispatch method is workflow_dispatch, the ID or the workflow file name to dispatch conditional
event-type If the selected dispatch method is repository_dispatch, what event type will be triggered in the repository. conditional
workflow-inputs A JSON object that contains extra information that will be provided to the dispatch call false '{}'
discover A flag to enable the discovery of the Run ID from the dispatched workflow false false
starting-delay-ms The delay, in milliseconds, before executing the function for the first time. false 200
max-attempts The maximum number of times to attempt read-only GitHub API requests. false 5
time-multiple The starting-delay-ms is multiplied by the time-multiple to increase the delay between reattempts. false 2

Outputs

By default, this GitHub Action has no outputs. However, when discovery mode is enabled, the Run ID and Run URL become exposed as outputs. With the Run ID, you can create some powerful automation where the parent workflow can wait for the status of the child workflow using the codex-/await-remote-run GitHub Action.

Name Description
run-id The Run ID of the workflow that was dispatched
run-url The URL of the workflow that was dispatched
steps:
  - uses: lasith-kg/dispatch-workflow@v1
    id: wait-repository-dispatch
    name: 'Dispatch Using repository_dispatch Method And Wait For Run-ID'
    with:
      dispatch-method: 'repository_dispatch'
      event-type: 'deploy'
      repo: ${{ github.event.repository.name }}
      owner: ${{ github.repository_owner }}
      token: ${{ secrets.GITHUB_TOKEN }}
      discover: true
  - name: Await Run ID ${{ steps.wait-repository-dispatch.outputs.run-id }}
    uses: codex-/await-remote-run@v1
    with:
      token: ${{ secrets.GITHUB_TOKEN }}
      repo: ${{ github.event.repository.name }}
      owner: ${{ github.repository_owner }}
      run_id: ${{ steps.wait-repository-dispatch.outputs.run-id }}
      run_timeout_seconds: 300 # Optional
      poll_interval_ms: 5000 # Optional

Workflow Inputs

This action supports the ability to provide workflow inputs for both the repository_dispatch and workflow_dispatch method. However, both methods have their unique limitations.

repository_dispatch

Source: peter-evans/repository-dispatch # Client Payload

The Create a repository dispatch event API call allows a maximum of 10 top-level properties in the workflow inputs JSON. If you use more than that you will see an error message like the following.

No more than 10 properties are allowed; 14 were supplied.

For example, this payload will fail because the github object has more than 10 top-level properties.

workflow-inputs: ${{ toJson(github) }}

A simple work-around is that you can simply wrap the payload in a single top-level property. The following payload will succeed.

workflow-inputs: '{"github": ${{ toJson(github) }}}'

Additionally, there is a limitation on the total data size of the client-payload. A very large payload may result in the following error

client_payload is too large

workflow_dispatch

The Create a workflow dispatch event API call also sets the maximum number of top-level properties in the workflow inputs JSON to 10. Any default properties configured in the workflow file will be considered towards this count when inputs are omitted.

An additional requirement is that all top-level properties must be a string. Any inputs represented as a number or boolean will get rejected. Therefore values of these types must be wrapped in quotes to successfully dispatch the workflow.

# Invalid ❌
  - uses: lasith-kg/dispatch-workflow@v1
    id: workflow-dispatch
    name: 'Dispatch Using workflow_dispatch Method'
    with:
      dispatch-method: 'workflow_dispatch'
      ...
      workflow-inputs: |
        {
          "foo": true,
          "bar: 1
        }

# Valid 🟢
  - uses: lasith-kg/dispatch-workflow@v1
    id: workflow-dispatch
    name: 'Dispatch Using workflow_dispatch Method'
    with:
      dispatch-method: 'workflow_dispatch'
      ...
      workflow-inputs: |
        {
          "foo": "true",
          "bar: "1"
        }

Advanced Usage

Exponential Backoff

When interacting with the GitHub REST API, it's beneficial to handle potential flakiness by employing exponential backoff. This action allows users to customize this behavior through optional parameters, although the default values work well for most scenarios.

  • starting-delay-ms: The initial delay, in milliseconds, before the first API call attempt.
  • max-attempts: The maximum number of times to attempt read-only GitHub API requests.
  • time-multiple: The factor by which the starting-delay-ms is multiplied for each reattempt, influencing the delay duration.
  - uses: lasith-kg/dispatch-workflow@v1
    id: custom-backoff
    name: 'Dispatch with custom exponential backoff parameters'
    with:
      ...
      starting-delay-ms: 150
      max-attempts: 3
      time-multiple: 1.5