How to Send More Than Ten Inputs with Workflow Dispatch
There is a limit of ten inputs on the number of inputs you can send when dispatching a GitHub workflow.
- The maximum number of top-level properties for
inputs
is 10.
However, with highly parameterized workflows, I quickly hit the limit. In my case, I had a Cypress workflow with the following inputs:
on:
workflow-dispatch:
inputs:
name: ... #nice test name in workflow RUN UI
php_version: ...
db_version: ... #specific version of maria/mysql/percona
db_type: ... #maria/percona/mysql
mailpit_version: ... #version of the docker image with mailpit
template: ... # which app template to use in cypress?
project: ... # which project to setup for cypress test
split: ... # to how many jobs split cypress test suite
record_video: ... # record vido of cypress tests
flaky_report: ... # report flaky tests?
# 10 inputs in total
I needed to add one more input, send_slack_notification: true/false
, to be able to disable Slack notification sending and do it from elsewhere (parent workflow).
For dispatching, as usual, I was using my favorite dispatch action workflow-dispatch-and-wait. However, I was facing several major issues with it:
It didn’t help with my ten inputs limit problem.
Setting many inputs in JSON is less readable and complex to extend.
Action was not connecting the dispatching and dispatched workflows in any way (see my previous article How to connect dispatching and dispatched workflows about this topic).
Fortunately, the action is very simple and easy to extend. So, I extended it to solve all my problems mentioned above:
To have more than 10 inputs (up to 65k payload limit, more on that later).
Use YAML in YAML (yeah, sounds crazy but far better than serialized JSON) for writing inputs - improved readability.
Dispatcher automatically send meta into dispatched workflow - no more extra steps to prepare the data.
Let’s break it down to three simple steps.
Allow Sending More Than 10 Inputs
My dispatching workflow originally looked like this:
#...snip
jobs:
run-cypress:
runs-on: ubuntu-latest
- name: Start cypres tests
uses: the-actions-org/workflow-dispatch@v4
with:
workflow: cypress-run.yaml
token: ${{ secrets.GITHUB_TOKEN }}
# max. 10 inputs here
# had to be json 🤮
inputs: >-
{
"name": "Cypress dark",
"php_version": "8.3",
"db_version": "10.11.8",
"db_type": "mariadb",
"mailpit_version": "latest",
"template": "dark",
"project": "cy_test_prj1",
"split": "2",
"record_video": "false",
"flaky_report": "true"
}
What I did to support more inputs was reserve one input named meta
and put all extra inputs there.
My dispatching workflow would now look like this:
#...snip
jobs:
run-cypress:
runs-on: ubuntu-latest
- name: Start cypres tests
uses: the-actions-org/workflow-dispatch@v4
with:
workflow: cypress-run.yaml
token: ${{ secrets.GITHUB_TOKEN }}
# max. 10 inputs here
inputs: >-
{
"name": "Cypress dark",
"php_version": "8.3",
"db_version": "10.11.8",
"db_type": "mariadb",
"mailpit_version": "latest",
"template": "dark",
"project": "cy_test_prj1",
"split": "2",
"record_video": "false",
# if using JSON, meta's value have to be stringified 🤮
"meta": "{\"flaky_report\": \"true\", \"send_slack_notification\":\"false\"}"
}
Of course, one input named meta
in the dispatched workflow has to be reserved to receive the data:
on:
workflow_dispatch:
inputs:
#...other inputs
meta:
description: 'Meta information to pass to the workflow. JSON string.'
required: false
default: ''
Later on, in the dispatched workflow, you can easily decode whatever you need from the meta
input using the fromJSON expression like this:
steps:
- name: Notify slack
if: ${{ fromJSON(inputs.meta).send_slack_notification == 'true' }}
run: |
slack-notify-cli --channel=info --message="Workflow successful"
Now I can send as many inputs to my dispatched workflows as I want 🎉!
The meta
field is stringified so I can conveniently work with it using the fromJSON expression.
meta
field now have to be always serialized JSON. Which is hard do maintain and read.# meta field must be always encoded JSON 😢
"meta": "{\"flaky_report\": \"true\", \"send_slack_notification\":\"false\"}"
This might be fine for one or two extra inputs, but it is not manageable or readable for anything more than that.
YAML to the Rescue
I realized that, with a little help from the yaml npm library, I can rewrite my dispatching workflow to look like this:
#...snip
jobs:
run-cypress:
runs-on: ubuntu-latest
- name: Start cypres tests
uses: the-actions-org/workflow-dispatch@v4
with:
workflow: cypress-run.yaml
token: ${{ secrets.GITHUB_TOKEN }}
inputs: |
name: Cypress dark
php_version: 8.3
db_version: 10.11.8
db_type: mariadb,
mailpit_version: latest
template: dark
project: cy_test_prj1
split: 2
record_video: false
meta:
flaky_report: true
send_slack_notification: false
Much cleaner, I think:
no more unnecessary quotations around keys or values
no JSON at all
as many inputs under the
meta
key as I want
I only needed to change the action’s parse method to this (slightly simplified version):
function parse(inputsJsonOrYaml: string) {
if(inputsJsonOrYaml) {
//let's try to parse JSON first
try {
const parsedJson = JSON.parse(inputsJsonOrYaml)
//okay it was a valid JSON
return parsedJson
} catch(e) {
core.debug(`Failed to parse inputs as JSON: ${(e as Error).message}`)
//original method would end here with error
}
//ok, it wasn't a valid JSON, let's try again with YAML
const parsedYaml = YAML.parse(inputsJsonOrYaml)
if (typeof parsedYaml !== 'object') {
//inputs must be an object, otherwise it doesn't make sense
const error = new TypeError('Failed to parse \'inputs\' parameter. Must be a valid JSON or YAML.');
core.setFailed(error)
throw error
}
core.debug('Inputs parsed as YAML')
//ok inputs were parsed as YAML
//stringify inputs internally, because those are sent
//to the dispatched workflow through REST call
parsedYaml.meta = JSON.stringify(parsedYaml.meta)
return parsedYaml
}
return {}
}
Connecting Dispatching and Dispatched Workflow
The last thing that annoyed me was that the dispatched workflow didn’t know which workflow dispatched it.
I wrote about how this can be solved in a previous article of this series called How to connect dispatching and dispatched workflows.
The only problem was that I had to manually pass the data when dispatching:
#...snip
jobs:
run-cypress:
runs-on: ubuntu-latest
- name: Start cypres tests
uses: the-actions-org/workflow-dispatch@v4
with:
workflow: cypress-run.yaml
token: ${{ secrets.GITHUB_TOKEN }}
inputs: |
name: Cypress dark
php_version: 8.3
db_version: 10.11.8
db_type: mariadb,
mailpit_version: latest
template: dark
project: cy_test_prj1
split: 2
record_video: false
meta:
# always pass following 3 inputs :(
workflow-name: ${{github.workflow}}
workflow-url: ${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}/attempts/${{github.run_attempt}}
workflow-repo: ${{github.repository}}
another-key: another value
So I extended my action again to handle it automatically. Again all that was needed, to slightly update the parse method (simplified version):
function parse(inputsJsonOrYaml: string) {
if(inputsJsonOrYaml) {
//let's try to parse JSON first
try {
const parsedJson = JSON.parse(inputsJsonOrYaml)
//okay it was a valid JSON
core.debug('Inputs parsed as JSON')
return parsedJson
} catch(e) {
core.debug(`Failed to parse inputs as JSON: ${(e as Error).message}`)
}
//ok, it wasn't a valid JSON, let's try again with YAML
const parsedYaml = YAML.parse(inputsJsonOrYaml)
if (typeof parsedYaml !== 'object') {
//inputs must be an object, otherwise it doesn't make sense
const error = new TypeError('Failed to parse \'inputs\' parameter. Must be a valid JSON or YAML.');
core.setFailed(error)
throw error
}
core.debug('Inputs parsed as YAML')
//ok inputs were parsed as YAML
if (!parsedYaml.meta) {
//if there was no `meta` input, initialize it
parsedYaml.meta = {}
}
//add info about self, for dispatched workflow
parsedYaml.meta.workflow_name = github.context.workflow
parsedYaml.meta.workflow_url = `${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}/attempts/${parseInt(process.env.GITHUB_RUN_ATTEMPT as string)}`
parsedYaml.meta.workflow_repo = `${github.context.repo.owner}/${github.context.repo.repo}`
//stringify inputs back,
//so I can work with with fromJSON expression
parsedYaml.meta = JSON.stringify(parsedYaml.meta)
return parsedYaml
}
return {}
}
Now I can simply dispatch like:
#...snip
jobs:
run-cypress:
runs-on: ubuntu-latest
- name: Start cypres tests
uses: the-actions-org/workflow-dispatch@v4
with:
workflow: cypress-run.yaml
token: ${{ secrets.GITHUB_TOKEN }}
# finally clean & clear 🥳
inputs: |
name: Cypress dark
php_version: 8.3
db_version: 10.11.8
db_type: mariadb,
mailpit_version: latest
template: dark
project: cy_test_prj1
split: 2
record_video: false
meta:
send_slack_notification: false
And get all the data I need in the dispatched workflow with the fromJSON expression like this:
name: Some dispatched workflow
on:
workflow_dispatch:
inputs:
#...mx. 9 primary inputs...
meta:
description: 'Meta information to pass to the workflow. JSON string'
required: false
default: ''
jobs:
echo:
runs-on: ubuntu-latest
steps:
# display dispatching workflow info and connecting link
- name: Parent info
if: ${{ inputs.meta != '' && fromJSON(inputs.meta).workflow_name != '' }}
shell: bash
run: |
echo 'Dispatched from [${{ fromJSON(inputs.meta).workflow_name }}](${{ fromJSON(inputs.meta).workflow_url }}) in repo `${{ fromJSON(inputs.meta).workflow_repo }}`' >> $GITHUB_STEP_SUMMARY
# read any value from meta
- name: Parse meta to outputs
if: ${{ inputs.meta != '' && fromJSON(inputs.meta) != '' }}
id: meta
shell: bash
run: |
echo "Extra inputs from meta:" >> $GITHUB_STEP_SUMMARY
echo '- some key: `${{ fromJSON(inputs.meta).send_slack_notification }}`' >> $GITHUB_STEP_SUMMARY
- name: Send slack notification
if: ${{ fromJSON(inputs.meta).send_slack_notification == 'true' }}
uses: some/action
You can find the complete extended action repository on my GitHub: roamingowl/workflow-dispatch-with-yaml-support.
Conclusion
With the simple extension of the original dispatch action workflow-dispatch-and-wait, I was able to bypass the dispatch inputs count limit and improve my workflows' readability.
There is a catch, though. The Inputs
field size can’t exceed the 65k payload limit. So this method is not ideal for transferring large data between workflows. I’ll address this problem next time (and how to solve it).
That is all for now. Thank you. 👋
Happy coding :)