cloudsoft.io

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 workflow
  • end: exit the workflow (or if in a block where this doesn’t make sense, such as retry, 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 backoff on-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 an exit_code greater than zero (which the ssh step treats as an error) this will go to the step with id: my-service-restart-error. Any other error, such as network connectivity, will be rethrown and could be addressed by a workflow-level on-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.