Conditional operator or function for expression syntax
Describe the enhancement
I'd like some kind of conditional operation added to expression syntax. This can be an actual ternary operator (? :) or a built-in function (e.g., if(<condition>, <true-value>, <false-value>)).
Additional information
There is a workaround: using shell scripts to evaluate the condition, setting step outputs, and having other steps reference those outputs. But it would be much cleaner to have some kind of ternary / if/then/else / conditional branching built in to the expression syntax.
Another alternative syntax could be:
${{ if(condition) }} evaluateIfTrue ${{ else }} evaluateIfFalse ${{ endif }}
like CircleCI.
It seems, although not identical, <condition> && <true-value> || <false-value> works in most cases.
Syntax: ${{ x && 'ifTrue' || 'ifFalse' }}
fake ternary example
steps:
- name: stuff
env:
PR_NUMBER_OR_MASTER: ${{ github.event.number == 0 && 'master' || format('pr-{0}', github.event.number) }}
on condition is that your first <true-value> must be true for this to work
Example use case https://github.com/TWiStErRob/net.twisterrob.gradle/pull/136/files Related StackOverflow: https://stackoverflow.com/a/68422069/253468
The fact that in a year no one said a word about this, means that the team isn't interested in following up on this one?
A note about the workaround ${{ x && <yes> || <false> }}:
This only works if <yes> isn't the empty string. If <yes> == '' then '' is considered as false, which then evaluates the right hand side of the ||. Just lost ten minutes over this, hope it helps!
Another word to the wise about the workaround, which is that it only works with single quotes. In other words ${{ (<boolean> && 'true value') || 'false value' }} works but ${{ (<boolean> && "true value") || "false value" }} is a syntax error.
[EDIT: So, it turns out you can never use double quotes inside an expression if you use ${{ ... }, which explains the above. They actually do say that here, but I'd missed it. Nonetheless, I'll leave this up in case anyone else misses it, too.]
Great workaround, but can someone from @actions triage this so we get proper support without edge cases?
In case it helps anyone else: I was trying to a conditional runs-on, doing something like:
runs-on: ${{ (inputs.shouldUseSelfHosted && [ 'self-hosted', 'Linux', 'X64' ] || 'ubuntu-latest' }}
But this was a syntax error, since I guess array literals don't work in expressions?
So then I tried wrapping it in quotes:
runs-on: ${{ (inputs.shouldUseSelfHosted && '[ ''self-hosted'', ''Linux'', ''X64'' ]') || 'ubuntu-latest' }}
But while this is syntactically correct, and it works for the falsy case ('ubuntu-latest'), the truthy case doesn't seem to properly resolve a runner—presumably becaues the value type should be an array, not a string.
Finally, after some trial an error, I arrived at a working solution:
${{ (inputs.shouldUseSelfHosted && fromJSON('[ "self-hosted", "Linux", "X64" ]')) || 'ubuntu-latest' }}
The fromJSON properly converts the string into an array when it evaluates, and resolves to a valid array for github purposes.
Anyway, uh, yeah, this would all be a lot nicer with ternaries/an if function 😓
It supports workflow_call as well as a manual triggers which specific to stage
runs-on: ${{ (inputs.shouldUseSelfHosted || github.event.inputs.stage == 'prod') && fromJSON('[ "self-hosted", "Linux", "X64" ]') || 'ubuntu-latest' }}
I found that using fromJSON worked nicely for me if i had to deal with more complex conditionals
env:
IMAGE: ${{ fromJSON('{"main":"prod","dev":"sat"}')[github.ref_name] || github.ref_name }}
instead of
env:
IMAGE: ${{ (github.ref_name == 'main' && 'prod') || (github.ref_name == 'dev' && 'sat') || github.ref_name }}
3 years anniversary soon!
I think it is funny that GitHub is able to develop immense complex and scalable systems. But this simple conditional is not yet there 🤡
@andrewakim fyi 😄
Happy 3 year anniversary! 🎉
A note about the workaround
${{ x && <yes> || <false> }}:This only works if
<yes>isn't the empty string. If<yes> == ''then''is considered asfalse, which then evaluates the right hand side of the||. Just lost ten minutes over this, hope it helps!
You can bypass this limitation by constructing an array with the false and true branch values (in this order) using fromJSON(), then access the correct element by using the bracket notation and placing the condition inside - it is implicitly cast from a boolean to a number, false => 0, true => 1, acting as an array index:
${{ fromJSON('["other value", "empty value"]')[something == ''] }}
Seems that there is already an approach
https://docs.github.com/en/actions/learn-github-actions/expressions#example
env:
MY_ENV_VAR: ${{ github.ref == 'refs/heads/main' && 'value_for_main_branch' || 'value_for_other_branches' }}
I think this issue can be closed.
@islishude you ~~can't~~ shouldn't just comment on an issue that hundreds of people have subscribed to, just to say "this can be closed" when your suggested approach (and its limitation) has already been mentioned. see https://github.com/actions/runner/issues/409#issuecomment-1013325196
Interesting, you saying I can't comment, is the comment illegal?
you can't just comment on an issue that hundreds of people have subscribed to
It's good to see GitHub embracing the workaround as a stop-gap, they even mention its limitation in the docs @islishude linked.
On one hand it's good they added it, because at least now it's easier to discover, but on the other hand I do hope it doesn't mean they consider ternary "supported".
It would be great to get some official response from GitHub staff. 🤔
It's ridiculous that we do not have ternary operator and this issue is 3+ y.o. Github actions is so silly engineered, will avoid it for any future projects by all means.
Correct answer for booleans ${{ (condition && 'ifTrue') || (!condition && 'ifFalse') }}
It's good to see GitHub embracing the workaround as a stop-gap, they even mention its limitation in the docs @islishude linked.
The docs state, "In this example, we're using a ternary operator to set the value of the MY_ENV_VAR environment variable," which is laughable, because there is no ternary operator, just a logical kludge that relies on short-circuit boolean evaluation:
env:
MY_ENV_VAR: ${{ github.ref == 'refs/heads/main' && 'value_for_main_branch' || 'value_for_other_branches' }}
Great exposition on just why this is a poor solution: https://7tonshark.com/posts/github-actions-ternary-operator/
Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the GitHub Community Support Forum which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please read our contribution guidelines. 😃
Looks more as unsolved than "completed"
Couldn't find an existing thread there so I created one: https://github.com/orgs/community/discussions/75928
Hi everyone, we just announced our new case function which is adds conditionals for expressions.
BTW it seems that this example from the case docs is not valid YAML:
env:
MY_ENV_VAR: ${{ case(
github.ref == 'refs/heads/main', 'production',
github.ref == 'refs/heads/staging', 'staging',
startsWith(github.ref, 'refs/heads/feature/'), 'development',
'unknown'
) }}
The ) }} is not indented, so is not considered part of the MY_ENV_VAR value. Maybe this, with double quotes?
env:
MY_ENV_VAR: "${{ case(
github.ref == 'refs/heads/main', 'production',
github.ref == 'refs/heads/staging', 'staging',
startsWith(github.ref, 'refs/heads/feature/'), 'development',
'unknown'
) }}"
Also, I'm seeing errors like this when using non-boolean values as predicates in case:
The workflow is not valid. Error when evaluating 'run-name'. .github/workflows/my-workflow.yml (Line: 2, Col: 11): Error from function 'case': case predicate must evaluate to a boolean value
but the expressions docs say:
Note that in conditionals, falsy values (
false,0,-0,"",'',null) are coerced tofalseand truthy (trueand other non-falsy values) are coerced totrue.
Is there a reason that case doesn't coerce non-falsy values to true?
@lpulley we've fixed the invalid YAML in the docs,
env:
MY_ENV_VAR: |-
${{ case(
github.ref == 'refs/heads/main', 'production',
github.ref == 'refs/heads/staging', 'staging',
startsWith(github.ref, 'refs/heads/feature/'), 'development',
'unknown'
) }}
Thanks for calling that out!
As for the boolean values, we intentionally require predicates to be booleans to avoid common failure scenarios that unintentionally or incorrectly rely on falsy coercions. I'll take a look to see how we can improve the docs in regards to that as well.