Common Step Properties
Shorthand and Longhand Syntax
Where possible, and in most examples so far, we’ve used the “shorthand” syntax for steps which uses a string template to make it easy to write workflow steps:
steps:
- ssh echo today is `DATE`
- http http://some-rest-api/
- container my/container command
- set-sensor the-output = ssh says ${1.stdout}, http ${2.data}, container ${3.stdout}
This always starts with the type name, and the remainder is parsed according to a template defined by that step type.
Internally, steps are maps with multiple inputs and properties,
and the shorthand syntax makes it possible to set the most common ones.
For ssh
this is the command to run; for container
an image; for http
a URL; and for set-xxx
an expression of the form key=value
.
It is always possible to write the longhand map syntax, and if using inputs or properties
which aren’t supported in the shorthand template (such as the condition
property described below),
the longhand map syntax is required:
steps:
- type: ssh
input:
command: echo today is `DATE`
condition:
target: ${skip_date}
not: { equals: true }
- ...
Shorthand can be combined as part of a map by providing the step shorthand string in a key
step
(or s
or shorthand
). The type must be included as the first word, and the type
key must not be used.
steps:
- step: ssh echo today is `DATE`
condition:
target: ${scratch.skip_date}
not: { equals: true }
Care should be taken when using :
in a step with shorthand. YAML will parse it as a map if it is not quoted in YAML.
However at runtime, if the step looks like it came from an accidental colon causing a map, it will be reverted to a
string with the colon re-introduced, so you can write steps with shorthand - log Your name is: ${name}
.
All steps support a number of common properties, described below.
Explicit IDs and Name
Steps can define an explicit ID for use with next
, for correlation in the UI,
and to be able to reference the output or input from a specific step using
the workflow expression syntax.
They can also include a name
used in the UI.
steps:
- type: no-op
next: skipping-ssh-date
condition:
target: ${scratch.skip_date}
equals: true
- step: ssh echo today is `date`
name: Doing SSH
next: end
- id: skipping-ssh-date
name: Not doing SSH
step: log skipping ssh date command
Conditions
The previous example shows the use of conditions, as mentioned as one of the properties common to all steps.
This makes use of the recent Predicate DSL conditions framework.
It is normally necessary to supply a target
, unless one of the entity-specific target keys (e.g. sensor
or config
)
is used. The target and arguments here can use the workflow expression syntax.
The condition is evaluated when the step is about to run, and if the condition is not satisfied,
the workflow moves to the following step in the sequence, or ends if that was the last step.
Apart from name
and id
above, if a step’s condition
is unmet,
the other properties set on a step are ignored.
The if
step can be used for simple conditions, as shown in the next section.
Jumping with “Next” or “Goto”
The common property next
allows overriding the workflow sequencing,
indicating that a different step should be gone to next.
This does not apply if a step’s condition is not satisfied, as noted at the end of the previous section.
The value of the next
property should be the ID of the step to go to
or one of the following reserved words:
start
: return to the start of the workflowend
: exit the workflow (or if in a block where this doesn’t make sense, such asretry
, go to the last executed step)exit
: if in an error handler, exit that error handler
The goto
step type is equivalent to the no-op
step with next
set,
as a simpler idiom for controlling workflow flow.
While goto
is “considered harmful” in many programming environments,
for declarative workflow it is fairly common, because it can simplify what
might otherwise involve multiple nested workflows.
That said, the confusion that goto
can cause should be kept in mind,
and its availability not abused: in particular where a task can be better done
in a proper high-level programming language, consider putting that program
into a container and calling it from your workflow.
Thus the above workflow can be written more concisely as:
steps:
- if ${scratch.skip_date} then goto skipping-ssh-date
- step: ssh echo today is `date`
next: end
- id: skipping-ssh-date
name: Not doing SSH
step: log skipping ssh date command
Input and Output
Most steps take input parameters and return output.
Many step-specific input parameters can be set in the shorthand, but not all.
All input parameters can be specified in an input
block.
It is also possible to customize the output from a step using an output
block.
For example:
- step: let target = aws
condition:
target: location
tag: aws
next: picked-target
- step: let target = azure
condition:
target: location
tag: azure
next: picked-target
- input:
location_name: ${entity.location.name}
step: log Unrecognized cloud ${location_name}, using default
output:
cloud: default
next: end
- id: picked-target
step: log Picked target ${target}
output:
cloud: ${target}
The above will return an output map containing a key cloud
and a value of either azure
, aws
, or default
.
In addition, a custom input
variable is passed to the third step.
(This is not the simplest way to write this logic, but it illustrates the concepts.)
This example also shows the expression syntax. More on inputs, outputs, variables, and expressions is covered here.
Timeout
Any step and/or an entire workflow can define a timeout: <duration>
,
where the <duration>
is of the form 1 day
or 1h 30m
.
If the step or workflow where this is present takes longer than this duration,
it will be interrupted and will throw a TimeoutException
.
Error Handling with on-error
Errors on a step and/or a workflow can use the on-error: <handler>
property to determine how
and error should be handled. The <handler>
can be:
-
a single step as a string, for instance
on-error: retry
, or to prevent infinite loops and introduce exponential backoffon-error: retry limit 4 backoff 5s increasing 2x
-
a single step as a map, possibly with a condition; if the condition is not met, the error is rethrown; for example:
- step: ssh systemctl restart my-service on-error: step: goto my-service-restart-error condition: target: ${exit_code} greater-than: 0
If the
ssh
command returns anexit_code
greater than zero (which thessh
step treats as an error) this will go to the step withid: my-service-restart-error
. Any other error, such as network connectivity, will be rethrown and could be addressed by a workflow-levelon-error
or could cause the workflow to fail. -
a list of steps, some or all with conditions and some or all with
next
indicated, as follows:
The list of steps will run sequentially, applying conditions to each.
The target of conditions in an error handler is the error itself, so
the DSL error-cause
predicate can be used, for example
error-cause: { java-instance-of: TimeoutException }
or
error-cause: { glob: "*server timeout*" }
.
The error handler will complete and be considered to have handled the error at the first step
where the condition is satisfied and which indicates a next
step (either a goto
or retry
step, or next
property).
and subsequent steps in the error handler will not run.
If all steps have conditions and none are met, the error handler will rethrow the error,
but otherwise, if one or more steps run and none of them throw errors or indicate a next
,
it will consider the error to be handled and go to the next step in the non-error-handler workflow.
Where the handler combines non-conditional statements (such as log
) with conditions,
all expected terminal conditions should indicate a next
; to avoid confusion it is not recommended that
the last step be a condition that might not apply. Consider adding a final step
fail rethrow message None of the error handler conditions were met
to make sure the handler does not
accidentally succeed because a log
step was run, when none of the “real” conditions applied.
The next
target exit
can be used in an error handler to indicate to go to the next step in the containing
workflow sequence. Nested error handlers are permitted, and exit
will return to the containing error handler
without indicating that it should exit. Any other next
target from a nested workflow jumps out of all nested
error handlers and goes to that target in the non-error-handler workflow.
Error handlers run in the same context as the original workflow, not a new context as nested workflow does, but with some restrictions. This has some significant benefits but also some things in special cases which might require care:
- You can read and write workflow variables within error handlers
- You can set the
output
for use in the outer workflow - Error handlers are not persisted; any replay will revert to the outer step or earlier
- ID’s are not permitted in error handlers; any
next
refers to the containing workflow - The workflow UI does not show error handling steps; their activity can only be seen in the tasks view and in the logs
Workflow Settings
There are a few other settings allowed on all or some steps which configure workflow behavior.
These are replayable
, idempotent
, and retention
,
and are described under Workflow Settings.