Job conditions
The if field on a job controls whether that job runs. It holds a boolean expression that is evaluated once the job's dependencies have finished. If the expression is true the job runs; if it is false the job is skipped.
jobs:
train:
steps:
- run: ./train.sh
notify-failure:
depends_on: [train]
if: failure()
steps:
- run: ./notify.sh "training failed"
deploy:
depends_on: [train]
if: inputs.deploy == 'true'
steps:
- run: ./deploy.sh
The same if field, with the same expression language, is available on steps - see Step conditions.
Expression syntax
An if value is a boolean expression written directly in the field. It is not wrapped in ${{ }}: the ${{ }} form is string substitution (see Expressions) and is a different mechanism. A condition is evaluated to true or false, not substituted into text.
if: always() && deps.train.state == 'FAILED'
${{ }} is not interpreted inside if and is rejected during validation. Reference values directly (inputs.deploy), not as ${{ .inputs.deploy }}.
Operators
| Operator | Meaning |
|---|---|
== | equal |
!= | not equal |
&& | logical and |
|| | logical or |
! | logical not |
( ) | grouping |
Precedence, from highest to lowest: !, then == and !=, then &&, then ||. Use parentheses to make grouping explicit:
if: always() && (deps.build.state == 'FAILED' || deps.test.state == 'FAILED')
Literals
- Strings, in single quotes:
'SUCCEEDED','prod'. - Booleans:
true,false. - Integers:
3,0.
Bracket form for references
A name that is a plain identifier - letters, digits, and underscores - can be written directly after a dot: deps.build.state, inputs.deploy. A name containing other characters, such as a hyphen, is written as a quoted name in brackets:
if: always() && deps['list-engines'].state == 'FAILED'
The two forms are equivalent; deps.build.state and deps['build'].state reference the same dependency. The bracket form works for any reference, so it also covers inputs['...'] and env['...'] names that are not plain identifiers.
Values you can reference
inputs.<name>
A workflow input. See Workflow Inputs.
if: inputs.environment == 'production'
env.<NAME>
An environment variable visible to the job. See Environment Variables.
if: env.STAGE == 'prod'
deps.<id>.state
The terminal state of a job this job depends on. <id> must be listed in this job's depends_on; referencing a job that is not a dependency is a validation error, because a non-dependency's state is not known when the condition is evaluated.
The possible state values are:
| Value | Meaning |
|---|---|
'SUCCEEDED' | the dependency completed successfully |
'FAILED' | the dependency failed (a canceled dependency also reports 'FAILED') |
'SKIPPED' | the dependency was skipped |
Comparing a dependency to 'SUCCEEDED' duplicates the success requirement, so deps.<id>.state is most useful together with always() or failure() - which lift that requirement - to branch on which dependency reached a given state:
report:
depends_on: [build, test]
if: always() && deps.test.state == 'FAILED'
steps:
- run: ./report.sh
deps.<id>.outputs.<name>
A value from a dependency's declared outputs - branching on an upstream job's computed value, not just its status. <id> must be listed in depends_on, and <name> must be an output that job declares (a typo or undeclared name is a validation error). An output that was not produced at runtime is an empty string.
jobs:
detect:
outputs:
changed: ${{ .steps.scan.outputs.changed }}
steps:
- id: scan
run: echo "changed=true" >> "$H2O_WORKFLOWS_OUTPUT"
deploy:
depends_on: [detect]
if: deps.detect.outputs.changed == 'true'
steps:
- run: ./deploy.sh
Referencing the outputs of a matrix dependency is not supported.
Status functions
Two functions summarize the outcome of a job's dependencies.
failure()
True when any job in depends_on ended in state FAILED, and it lifts the success requirement so the job can run despite that failure. For a job with depends_on: [a, b] it is equivalent to:
if: failure()
if: always() && (deps.a.state == 'FAILED' || deps.b.state == 'FAILED')
always()
Always true. The job runs regardless of its dependencies' outcomes.
cleanup:
depends_on: [train]
if: always()
steps:
- run: ./cleanup.sh
The success requirement
By default, a job runs only after all of its depends_on jobs succeed. Adding an if condition keeps that success requirement and applies it together with the condition: the job runs when all dependencies succeeded and the condition is true.
The failure() and always() functions are the exception: using either one lifts the success requirement, so the job can run even when a dependency failed or was skipped. This is what makes failure-handling and cleanup jobs possible. Any other condition is applied on top of the requirement, so a condition that happens to be always true still runs only when the dependencies succeeded:
| Condition | Runs when |
|---|---|
(no if) | all dependencies succeeded |
if: inputs.deploy == 'true' | all dependencies succeeded and the condition is true |
if: true | all dependencies succeeded (an always-true condition does not lift the requirement) |
if: failure() | any dependency failed |
if: always() | any outcome (the success requirement is lifted) |
Matrix jobs
When a job defines a matrix, its if is evaluated per instance - each generated instance decides independently whether it runs.
When a job depends on a matrix job, deps.<id>.state is the aggregate across all of that matrix's instances: 'SUCCEEDED' only if every instance succeeded, and 'FAILED' if any instance failed. This matches the rule that depending on a matrix job waits for all of its instances.
Skipped jobs
A job whose condition excludes it ends in the terminal state SKIPPED. A skipped job:
- does not fail the run - a run whose jobs are all succeeded or skipped is successful;
- does not trigger
cancel_on_failure; - causes its own dependents to be skipped in turn, unless those dependents use
failure()oralways().
See Failure handling for how skipping interacts with cancel_on_failure.
Validation
Conditions are checked when the workflow is saved:
- the expression must be syntactically valid;
- every
deps.<id>must name a job in this job'sdepends_on(a step-scopesteps.<id>reference is not allowed in a job condition); - referenced
inputsandenvnames must exist.
Examples
Route on success and failure:
jobs:
train:
steps:
- run: ./train.sh
notify-success:
depends_on: [train]
# no if: needed - a job runs by default only when its dependencies succeed
steps:
- run: ./notify.sh ok
notify-failure:
depends_on: [train]
if: failure()
steps:
- run: ./notify.sh fail
Gate a deploy on an input, still requiring the build to succeed:
deploy:
depends_on: [build]
if: inputs.deploy == 'true'
steps:
- run: ./deploy.sh
Always clean up, whatever happened upstream:
cleanup:
depends_on: [train]
if: always()
steps:
- run: ./cleanup.sh
Combining job and step conditions
A job condition and step conditions compose: the job-level if decides whether the job runs at all, and step-level if decides the flow within it. See Combining job and step conditions for a worked example.
- Submit and view feedback for this page
- Send feedback about H2O Workflows to cloud-feedback@h2o.ai