It's easy to do, actually, but finding out how is oddly difficult.
The documentation is marvelous at telling you all the options, but not always great for giving examples.
So the trick is, in your workflow, do this:
setup-dns:
name: Setup dynamic DNS
runs-on: prod
Assuming, of course, that your label is 'prod'. (BTW, don't make Prod use dynamic DNS). According to the docs and common sense, this should work:
setup-dns:
name: Setup dynamic DNS
runs-on: [self-hosted, prod]
But it does not; the runner never runs. As long as you don't create a self-hosted runner with a label (not name) of 'ubuntu-latest
' (or other GitHub hosted runner O/S versions) you won't have a problem!
When you think about it logically, putting in your own label requires that it's a self-hosted runner, so the two values for "runs-on" are somewhat redundant. My guess is that you could still use two self-hosted runner labels, and the routine would run on whichever one was available. We didn't go that route as we needed patches run on multiple production servers deterministically, so we literally have prod1, prod2, prod2, etc.
Now here's where it gets interesting. Let's say that you have a callable workflow - better known as a "Subroutine". In that case, you cannot use "runs-on
" in the caller; only the callee.
So here's what you do, in your callable workflow:
on:
workflow_call:
inputs:
target_runner_label:
type: string
description: 'Target GitHub Runner'
required: true
# not strictly needed, but great for testing your callable workflow
workflow_dispatch:
inputs:
target_runner_label:
type: string
description: 'Target GitHub Runner'
required: true
jobs:
report_settings:
name: Report Settings
runs-on: ${{ inputs.target_runner_label || github.event.inputs.target_runner_label }}
Now - you might wonder at the ||
statement in the middle. It turns out, the syntax for referring to workflow_call
inputs and workflow_dispatch
inputs are completely different, so the concat ensures that, if it's called via workflow_dispatch OR workflow_call, the inputs are received. The other input will be ignored. You have to do this doubling up everywhere. WET code, sigh.
Another gotcha: The 'name' of the runner is completely superfluous, because it's the label, not the name, that the workflow uses to trigger it. In the below example, you wouldn't use "erpnext_erpdev" to trigger the workflow, you'd use "erp_fix"
