Skip to content

Action to create workflow dispatch events and wait for completion

License

Notifications You must be signed in to change notification settings

mathze/workflow-dispatch-action

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Workflow dispatch action

An action that creates a workflow dispatch event and returns the run-id of started workflow. This action can also be used to wait on completion of the triggered workflow.

About

In contrast to other existing dispatch-actions, this action provides a way to reliably identify the workflow run (see With marker step).
To provide most flexibility, the action supports three different modes.
Thereby the mode is controlled by the two input parameters workflow-name and run-id.

  1. 'Trigger' mode:
    In this mode the action triggers the workflow and just tries to receive the workflow run id. This can be used if you want to get the run id and wait for it in another step/job later on. This mode will be enabled if the workflow-name input is present.

  2. 'Wait' mode:
    In this mode the action waits until a workflow run finishes. This can be used in combination with 1. mode or if you get the run id from somewhere else. The mode will be enabled if run-id is given. In this case the run-id is expected to be valid otherwise it results in error.

  3. 'Trigger and wait' mode:
    This is a combination of 1. and 2. mode. To enable it, specify the workflow-name and run-id inputs. In this case, the run-id isn’t required to be valid because it gets replaced by the one detected in the trigger step.

Usage

In addition to the examples shown here, you can also take a look at the .github/workflows/ folder. There we have a few workflows for test purposes that might inspire you.

Simple (flaky) usage

The following examples show the minimum setup without using a marker step. This mode is handy if you know that there will be only on workflow run at a time.

With direct wait
#...
  - name: "Start and wait for a workflow"
    id: startAndWaitWorkflow
    uses: mathze/[email protected]
    with:
      workflow-name: my-workflow.yml
      token: ${{ secrets.MY_PAT }}
      run-id: dummy
#...
  - name: "Reuse workflow run id"
    run: "echo ${{ steps.startAndWaitWorkflow.outputs.run-id }}
Trigger and independent wait
#...
  - name: "Start a workflow"
    id: startWorkflow
    uses: mathze/[email protected]
    with:
      workflow-name: my-workflow.yml
      token: ${{ secrets.MY_PAT }}
#...
  - name: "wait to complete"
    uses: mathze/[email protected]
    with:
      token: ${{ secrets.MY_PAT }}
      run-id: ${{ steps.startWorkflow.outputs.run-id }}
💡
You also can create a 'fire & forget' workflow by simply omitting the 'wait-to-complete' step.

With marker step

Caller workflow ('Trigger and independent wait' case)
#...
  - name: "Start workflow"
    id: startWorkflow
    uses: mathze/[email protected]
    with:
      workflow-name: workflow-with-marker.yml
      token: ${{ secrets.MY_PAT }}
      use-marker-step: true
#...
  - name: "wait to complete"
    uses: mathze/[email protected]
    with:
      token: ${{ secrets.MY_PAT }}
      run-id: ${{ steps.startWorkflow.outputs.run-id }}
workflow-with-marker.yml
on:
  workflow-dispatch:
    inputs:
      external_ref_id: #(1)
        description: Id to use for unique run detection
        required: false
        type: string
        default: ""
jobs:
  beforeAll:
    runs-on: ubuntu-latest
    steps:
      - name: ${{ github.event.inputs.external_ref_id }} #(2)
        run: echo
  1. The target workflow has to define the external_ref_id input

  2. Here we define a step with the name of the passed input.

With additional payload

In this section we show how to configure the payload input of this action in two scenarios, with and without marker-step.

⚠️
Be careful when using secrets within payload! They might get exposed in the target-workflow!

No marker step

First, lets assume we have the following (simple) workflow we want to trigger through our action

say-hello.yml
on:
  workflow_dispatch:
    inputs:
      whom-to-greet:
        required: false
        description: Whom to greet
        default: "World"
        type: string

jobs:
  greetJob:
    runs-on: ubuntu-latest
    steps:
      - name: Greet
        run: |
          echo "::notice title=Greet::Hello ${{ github.event.inputs.whom-to-greet }}"

Now, the step in our calling workflow could look like this

#...
  - name: "Start say hello"
    id: startSayHello
    uses: mathze/[email protected]
    with:
      workflow-name: say-hello.yml
      token: ${{ secrets.MY_PAT }}
      payload: | #(1)
        {
          "whom-to-greet": "${{ github.actor }}" #(2)
        }
#...
  1. We use multiline string (indicated by '|'). This allows us to write the json in a more natural way.

  2. We only need the 'inputs' argument names — in this case "whom-to-greet" — and the value that shall be submitted. We also can use github expressions (even for/within the argument’s name).

With marker step

say-hello-with-marker.yml
on:
  workflow-dispatch:
    inputs:
      external_ref_id:
        description: Id to use for unique run detection
        required: false
        type: string
        default: ""
      whom-to-greet:
        required: false
        description: Whom to greet
        default: "World"
        type: string
jobs:
  greetJob:
    runs-on: ubuntu-latest
    steps:
      - name: ${{ github.event.inputs.external_ref_id }}
        run: echo

      - name: Greet
        run: |
          echo "::notice title=Greet::Hello ${{ github.event.inputs.whom-to-greet }}"

The respective step in our calling workflow could look like this

#...
  - name: "Start say hello"
    id: startSayHello
    uses: mathze/[email protected]
    with:
      workflow-name: say-hello-with-marker.yml
      token: ${{ secrets.MY_PAT }}
      use-marker-step: true
      payload: | #(1)
        {
          "whom-to-greet": "${{ github.actor }}" #(2)(3)
        }
#...
  1. We use multiline string (indicated by '|'). This allows us to write the json in a more natural way.

  2. We only need the 'inputs' argument names — in this case "whom-to-greet" — and the value that shall be submitted. We also can use github expressions (even for/within the argument’s name).

  3. Note that you do not need to specify the external_ref_id input, as it will be added automatically when use-marker-step is enabled.

Inputs

Input Description Required/
Optional
Default

owner

Organization or user under which the repository of the workflow resist.

O

Current owner

repo

Name of the repository the workflow resist in.

O

Current repository

token

The token used to work with the API.
The token must have repo scope.

‼️
Because token is used to also trigger dispatch-event,
you can not use the GITHUB_TOKEN as explained here

R

-

workflow-name

Name of the workflow to trigger. E.g. 'my-workflow.yml'.
(Enables trigger-mode)

conditional(M)

-

ref

The git reference for the workflow. The reference can be a branch or tag name.

ℹ️
If you want to use GITHUB_REF, make sure you
shorten it to the name only through
${{ GITHUB_REF#refs/heads/ }}

O

Default branch of the target repository.

payload

Json-String representing any payload/input that shall be sent with the dispatch event.

⚠️
Be careful when using secrets within payload!
They might get exposed in the target-workflow!

O

{}

trigger-timeout

Maximum duration(D) of workflow run id retrieval.

O

1 minute

trigger-interval

Duration(D) to wait between consecutive tries to retrieve a workflow run id.

O

1 second

use-marker-step

Indicates if the action shall look for a marker-step to find the appropriate run.

O

false

run-id

A workflow run id for which to wait.
(Enables wait-mode)

conditional(M)

-

wait-timeout

Maximum duration(D) to wait until a workflow run completes.

O

10 minutes

wait-interval

Duration(D) to wait between consecutive queries on the workflow run status.

O

1 second

fail-on-error

Defines if the action should result in a workflow failure if an error was discovered.

ℹ️
Errors in the inputs of this action are not
covered by the flag and always let the action
and the workflow fail.

O

false

(D): Duration can be specified in either ISO-8601 Duration format or in specific format e.g. 1m 10s (details see https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/parse.html)

Outputs

Output Type Description

failed

Boolean

Indicates if there was an issue within the action run, and the workflow may not have been triggered correctly or didn’t reach the completed status. To drill down the cause you can check the run-id and run-status outputs.

run-id

String

The run id of the started workflow. May be empty if no run was found or in case of an error.

run-status

String

The status of the triggered workflow. (Normally always 'completed')
Only set through the wait mode. May be empty if no run was found or in case of an error.

run-conclusion

String

The conclusion of the triggered workflow.
Only set through the wait mode. May be empty if no run was found or in case of an error.

How it works

Trigger-mode
  1. Determine workflow id for given workflow-name

  2. If use-marker-step is enabled, generate a unique external_ref_id (<CURRENT_RUN_ID>-<CURRENT_JOB_ID>-<UUID>)

  3. Trigger dispatch event to target workflow and store the dispatch-date (also pass external_ref_id in input if enabled)

  4. Query workflow runs for the given workflow (-id) that are younger than dispatch-date and targeting the given ref
    The query use the etag to reduce rate-limit impact

  5. Filter found runs

    1. If use-marker-step is enabled

      1. Filter runs that are not 'queued'

      2. Get step details for each run

      3. Find the step with the name of generated external_ref_id

      4. Take first (if any)

    2. Else

      1. Order runs by date created

      2. Take first (if any)

        ℹ️
        All subsequent requests use etag's
  6. Repeat 4 and 5 until a matching workflow run was found or trigger-timeout exceeds. Between each round trip we pause for trigger-interval units.

  7. Return the found workflow run id or raise/log error (depending on failOnError)

Wait-mode

This is quite simple, with the former retrieved workflow-run-id we query the state of the workflow-run until it becomes complete (or wait-timeout exceeds). All queries uses etag's