# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

-
  section_name: Workflow Control
  section_intro: |
    The first group of built-in step types are those which control the flow of execution and local state within the workflow.

  steps:
    - name: let
      summary: An alias for `set-workflow-variable`
      shorthand: '`let [ TYPE ] VARIABLE_NAME [ = VALUE ]`'

    - name: set-workflow-variable
      summary: Sets the value of a workflow internal variable. The step `let` is an alias for this
      shorthand: '`set-workflow-variable [TYPE] VARIABLE_NAME [ = VALUE ]`'
      input: |
        * `variable`: either a string, being the workflow variable name, or a map, containing the `name` and optionally the `type`;
          the value will be coerced to the given type, e.g. to force conversion to an integer or to a bean registered type;
          the special types `yaml` and `json` can be specified here to force conversion to a valid YAML or JSON string;
          the `name` here can be of the form `x.key` where `x` is an existing map variable, to set a specific `key` within it
        * `value`: the value to set, with some limited evaluation as described [here](../variables.html)
        * `interpolation_mode`: whether to run interpolation, defaulting to `words` for shorthand or `full` if `value` is specified in a map;
           options are to run on the `full` value (not touching quotes), on `words` (unquoting words, and interpolating only if unquoted),
           or `disabled` (to prevent interpolation in the evaluation of this variable)
        * `interpolation_errors`: whether unresolvable interpolated expressions `fail` and return an error (the default), or to `ignore`
          them (leaving them in their interpolated form), or to replace them with a `blank` string
      output: the output from the previous step, or null if this is the first step

    - name: transform
      summary: Applies a transformation to a variable or expression
      shorthand: '`transform [TYPE] [ VALUE | "value" VALUE | "variable" VARIABLE_NAME | VARIABLE_NAME "=" VALUE ] "|" TRANSFORM`'
      input: |
        * `variable`: either a string, being the workflow variable name, or a map, containing a `name` and/or a `type`;
          if a variable name is supplied, it is set as the result of the transform,
          and if `value` is not supplied, its initial value is used as the input to the transform (update in place) 
        * `value`: the value to start with; if omitted a `variable` is required and its value used as the starting value;
          where the value might be confused with a variable name it must be explicitly offset with the keyword `"value"`;
          more information is [here](../variables.html)
        * `transform`: a string indicating a transform, or multiple transforms separated by `|`, where a transform can be
          * `trim` to remove leading and trailing whitespace on strings, null and empty string values from lists or sets, and any entry with a null key or null value in a map
          * `replace MODE PATTERN REPLACEMENT` to replace a pattern in a string with a replacement, supporting mode `regex`, `glob`, or `literal`
          * `merge [list|set|map] [deep] [wait] [lax]` to treat the words in the value as expressions to be combined;
            indicates the value will be a space-separated list of values, with quotes significant, usually including `$(vars)`;
            if a type is specified, all values must be compatible with that type, otherwise collection v map merge is inferred from the types;
            if `deep`, any maps containing mergeable values (lists or maps) are themselves merged deeply;
            if `wait`, unavailable sensor values are blocked on before merging;
            if `lax` then nulls, and if not waiting unavailable sensors, are silently removed
          * `prepend <item>` and `append <item>` to add `<item>` at the start or end, respectively, when the input to the transform is a list or a string
          * `join [<separator>]` to concatenate stringable items from a list
          * `split [limit <limit>] [keep_delimiters] [literal|regex] <delimiter>` to split a string around the given `<delimiter>`, optionally including the delimiters or limiting the count
          * `slice <start-index> <optional-end-index>` to take a list and return the sublist; a negative index is taken from the end
          * `remove <item-or-index>` to remove an entry given a key (for a map, ignoring if not found) or an index (for a list, negative index taken from the end, failing if out of bounds)
          * `wait` to wait on values (such as `${entity.attributeWhenReady...}`); must precede any expression that has to resolve values, such as `trim`)
          * `type TYPE` to coerce to registered type `TYPE`
          * `json [string|parse|encode]`: indicates the input should be converted as needed using JSON;
            if `string` is specified, the value is returned if it is a string, or serialized as a JSON string if it is anything else;
            if `parse` is specified, the result of `string` is then parsed to yield maps/lists/strings/primitives (any string value must be valid json);
            if `encode` is specified, the value is serialized as a JSON string as per JS "stringify", including encoding string values wrapped in explicit `"`;
            if nothing is specified the behavior is as per `parse`, but any value which is already a map/list/primitive is passed-through unchanged
          * `yaml [string|parse|encode]`: as per `json` but using YAML serialization and, 
            for anything other than `encode`, stripping any text before a `---` YAML document separator,
          * `bash [json|yaml] [string|encode]`: equivalent to the corresponding `json` or `yaml` transform, with `json` being the default, followed by bash-escaping and wrapping in double quotes;
            ideally suited for passing to scripts; `string` is the default and suitable for most purposes, but `encode` is needed where passing to something that expects JSON such as `jq`
          * `first`, `last`, `min`, `max`, `sum`, `average` and `size` are accepted for collections
          * `to_string`, `to_upper_case`, and `to_lower_case` are accepted for strings
          * `resolve_expression` to trigger a re-resolution of the value being transformed, eg if it has nested variable references that need resolution or has been explicitly prepended and appended with `${` and `}`;
            this is not done immediately but on the next step (or at the end), so the value can be piped to `wait` if desired
          * `get [<modifier>]` to get the unwrapped value of a supplier (if no arguments) or get a specific indexed value or modifier sequence,
            such as `get 0` or `get [0]` to get the first element of a list, or `get .key.subkey[0]` to take an element `key` in a map, an element `subkey` in the resulting map, and the first element of that list
          * `set VAR` to set the workflow variable `VAR` to the the value at that point in the transformation
          * `return` to return the result of the transformation (not compatible with supplying a `value` to set in a variable)
          * any other word is looked up as a registered type of type `org.apache.brooklyn.core.workflow.steps.transform.WorkflowTransform` (or `WorkflowTransformWithContext` to get the context or arguments)
      output: if no `value` is supplied, the result of all transformed; if a `value` is supplied and set in `variable`, then the output from the previous step, or null if this is the first step

    - name: clear-workflow-variable
      summary: Clears the value of a workflow internal variable
      shorthand: '`clear-workflow-variable [TYPE] VARIABLE_NAME`'
      input: |
        * `variable`: a string being the workflow variable name or a map containing the `name`
             of the workflow variable which should be cleared
      output: the output from the previous step, or null if this is the first step

    - name: return
      summary: |
        Returns an indicated value and specifies that the workflow should end,
        essentially equivalent to `{ type: no-op, output: VALUE, next: end }`.
      shorthand: '`return VALUE`'
      input: |
        * `value`: the value to return
      output: the value indicated

    - name: if
      summary: |
        Provides shorthand for a simple condition and step.
      shorthand: '`${condition_target} [ "==" ${condition_equals} ] [ " then " ${step...} ]`'
      input: |
        * `condition_target`: a condition to evaluate or an expression to check (for truthiness, unless a condition_equals is supplied)
        * `condition_equals`: a value to compare for equality against the supplied condition_target (which should not be a condition if this is used)
        * `step` or `steps`: a subworkflow to run if the condition passes
      output: the result of the supplied step or steps

    - name: wait
      summary: |
        Waits for a value which might not yet be resolved, or a task which might not have finished,
        optionally setting the value or the task's result to a workflow variable.
      shorthand: '`wait [ [TYPE] VARIABLE_NAME = ] [ "task" ] VALUE`'
      input: |
        * `variable`: a string being the workflow variable name or a map containing the `name` and optionally the `type`
             to coerce (needed e.g. if you want to set a bean registered type, or in shorthand to set an `integer`)
        * `task`: boolean to indicate that value should be treated as a task or task ID to wait on and take its result
        * `value`: the expression to wait on and optionally set
      output: |
        the value once available if a `variable` is not being set,
        or if a variable is being set the output from the previous step or null if this is the first step

    - name: load
      summary: |
        Loads the contents of a URL into a string or coerced to a type.
        Particularly useful when reading classpath resources.
      shorthand: '`load ["charset" CHARSET] [TYPE] VARIABLE_NAME = URL`'
      input: |
        * `variable`: a string being the workflow variable name or a map containing the `name` and optionally the `type`
             to coerce (needed e.g. if you want to set a bean registered type, or in shorthand to set an `integer`)
        * `url`: the URL to load
        * `charset`: the charset to use to process the input
        * `interpolation_mode`: whether to evaluated interpolated expressions in the content; default `disabled`; options are as for `let`
        * `interpolation_errors`: how to handle errors in interpolated expressions, if `interpolation_mode` is set; the default here is `ignore`
          which applies if `interpolation_mode` is set, and will leave unresolveable expressions untouched 
          so that such expressions can be resolved later by command shells or re-resolution; options are as for `let`
      output: |
        the output from the previous step or null if this is the first step

    - name: fail
      summary: Fails the workflow, throwing an exception which can be handled by an `on-error` block or returned to the user
      shorthand: '`fail [ "rethrow" ] [ "message" MESSAGE ]`'
      input: |
        * `message`: a string to report in the exception
        * `rethrow`: whether to include any error in scope (if being used within an `on-error` step)
        * `value`: on optional value that can be included in the failure, and for callers to inspect additional context
      output: _none_ (throws exception)

    - name: retry
      summary: Retries from an appropriate point with a configurable delay and back-off
      shorthand: '`retry [ "replay" ] [ "from" NEXT ] [ "limit" LIMIT_COUNT [ "in" LIMIT_TIME ] ] [ "backoff" BACKOFF ]`'
      input: |
        * `replay`: whether to do the retry as a replay or not; 
          if done as a replay, it reverts to the workflow variables as known at the target step;
          if a `next` step is specified the default is `false`, but if there is no `next` step
          the default is `true` and `next` is taken as `end` if in an error handler and `last` otherwise,
          so blank/default `retry` behavior in an error handler is to try to replay resuming 
          and in a normal workflow to replay from the last valid replay point;
          if `replay` is specified without next, the default `next` is the `last` replay point
          (always excluding the retry step itself, even if it is a valid replay point)
        * `limit`: a list of limit definitions, e.g. `[ "10", "1h", "2 in 1 min" ]` to restrict to a max of 10 retries total,
          an additional limit of 2 in any 1 minute period, and an additional limit of 1 hour; 
          as shorthand, per the syntax above, it takes one such limit definition;
          the retry will fail with a `RetriesExceededException` if any of these are exceeded;
          if it is intended to continue, it is necessary to attach an error handler to the retry step
        * `backoff`: a specification of how to delay, of the form `INITIAL [ "increasing" FACTOR ] [ "jitter" JITTER ] [ "up to" MAX ]`,
          where `INITIAL` is one or more durations applied to the respective iteration, and the last value applying to all
          unless an `increasing FACTOR` is supplied; 
          `FACTOR` as either a number followed by `x` to multiply, a number followed by by `%` to add a percentage each time, 
          or a duration to increase linearly; 
          `JITTER` as either a percentage or real factor by which to jitter the result with randomness, 0 being none, 100% being up to double, and -100% being down to zero;
          and `MAX` to specify a maximum duration; for example,
          `backoff 1s 1s 10s` will retry after 1 second the first two times, then after 10 seconds on subsequent instances; 
          and `backoff 1s 2s increasing 3x up to 1m` will retry after 1 second then 2s then 6s then 18s then 54s then 60s each subsequent time 
        * `hash`: an optional hash expression to identify related retries in a workflow instance; all retry blocks with the same `hash` will share counts
          for the purpose of counting limits (although the limits themselves are as per the definition in each retry block),
          which can be useful if there are different steps which might fail in different ways but the overall retry behaviour should be preserved
        * `next` per the [common](../common.html) step properties, with special targets 
          `last` for the last replayable step
          (the default if not in an error handler or if `replay` is specified) and 
          `end` to replay resuming (only permitted in an error handler where it is the default if `replay` is not specified)
      output: the output from the previous step, or null if this is the first step

    - name: goto
      summary: Moves execution to another step indicated by its ID
      shorthand: '`goto NEXT`'
      input: |
        * `next` per the [common](../common.html) step properties
      output: the output from the previous step, or null if this is the first step

    - name: switch
      summary: Concisely selects and runs the first matching step case, like `if ... else if ... else`
      shorthand: '`switch [ VALUE ]`'
      input: |
        * `cases`: a list of steps, all but the last with a `condition`, and the first applicable will be run, the others not;
          error if none match (where the last has a `condition`)
        * `value`: a value passed to each of the conditions as the `target`
      output: the output from the matched case step

    - name: workflow
      summary: |
        Runs nested workflow, optionally over an indicated target.
        This step type is described in more detail [here](../nested-workflow.html).
      shorthand: '`workflow [ replayable REPLAYBLE ] [ retention RETENTION ]` or custom'
      input: |
        * `steps`: a list of steps to run, run in a separate context
        * `target`: an optional target specifier, an entity or input to the steps for the sub-workflow,
          or if a list, a list of entities or inputs to pass to multiple sub-workflows
        * `target_var_name`: an optional variable name to set in nested workflows to refer to the element in the target, defaulting to `target`
        * `target_index_var_name`: an optional variable name to set in nested workflows to refer to the index of the element in the target, if the target is a list, defaulting to `target_index`
        * `concurrency`: a specification for how many of the sub-workflows can be run in parallel, if given a list target;
          defaulting to one at a time, supporting a DSL as described [here](../nested-workflow.html)
        * `reducing`: a map of variables to pass sequentially to each nested workflow instance,
          with the values of those variables in the output or scratch for each nested workflow passed to the next nested workflow,
          and the final set of those values being returned from this step for use in the calling workflow;
          not permitted if `concurrency` is not static at `1`
        * `condition`: the usual condition settable on steps as described [here](../common.html) can be used, with one difference
          that if a target is specified, the condition is applied to it or to each entry in the list, to conditionally allow sub-workflows,
          and the workflow step itself will always run (i.e. the condition does not apply to the step itself if it has a target)
        * `replayable`: instructions to add or modify replay points, as described [here](../settings.html), for example `workflow replayable from here`
        * `retention`: instructions to modify workflow retention, as described [here](../settings.html)
      output: if `reducing` is specified, the final value of those variables, 
        otherwise the output from the last step in the nested workflow or a list of such outputs if supplied a `target` list

    - name: subworkflow
      summary: |
        A simple set of steps to run, like `workflow` but more restricted in options,
        and sharing workflow variables with the containing workflow.
        No `target` or `concurrency` can be specified, and all containing workflow variables are in scope,
        
        A `name` can be given to a block of commands to improve understandability,
        A `condition` can be set on the entire block
        , but not allowing a target Runs nested workflow, optionally over an indicated target.
        This step type is described in more detail [here](../nested-workflow.html).
      shorthand: none; this step type is implied if there is no `step` shorthand or type indicated when there are `steps` defined
      input: |

    - name: foreach
      summary: |
        Runs nested workflow over a list using the specified variable name, equivalent to `workflow` when looping over a list,
        with support for including a single step as shorthand
      shorthand: '`foreach TARGET_VAR_NAME [ in TARGET [ do STEP ]]`'
      input: |
        * `step` or `steps`: a list of steps to run, run in a separate context
        * `target_var_name`: the name of the variable that should be set in nested workflows to refer to the element in the target list,
          with the additional behavior that if this is of the "spread map syntax" form `{KEY,KEY2}`, each element in the target list will be assumed to be a map
          and the keys "KEY" and "KEY2" will be extracted and each one set as a variable in the nested workflow
        * `target`: the list that should be looped over
        * other input as per `workflow`
      output: as per `workflow`, if `reducing` is specified, the final value of those variables,
        otherwise the list of outputs from the last step of each nested workflow corresponding to an element in the `target` list

-
  section_name: External Actions
  section_intro: |
    The next group of step types are those which typically do the "real work" by interacting with
    external resources in the real world.

  steps:
    - name: container
      summary: |
        Runs a container with optional command and environment variables.
      shorthand: '`container IMAGE [ COMMAND ]`'
      input: |
        * `image`: the image to run
        * Optionally a command to pass to the image, at most one of:
            * `command`: a command or script as a string, to pass to bash to be run
            * `commands`: a list of commands to pass to bash to be run
            * `raw_command`: a list containing the base executable in the first entry and any arguments as additional entries
        * `env`: a map of string keys with values whose JSON is taken and passed to the command be executed
        * `exit_code`: the word `any`, a number, or a predicate DSL expression (e.g. `less-than: 32`)
                         to require of the exit status code from the command, defaulting to `0`
        * `pull_policy`: one of `IfNotPresent`, `Always`, or `Never`, whether to pull the image before running;
             defaults to `IfNotPresent`
        * `output_max_size`: by default output from commands is limited to 100k, truncating to keep the end; this can be increased or decreased,
          or disabled with `-1` (assuming Apache Brooklyn has sufficient memory to hold the data)
      output: |
        * `stdout`
        * `stderr`
        * `exit_code`


    - name: http
      summary: Sends an HTTPS (or HTTP) request and returns the response content and code
      shorthand: '`http ENDPOINT`'
      input: |
        * `endpoint`: the URL to connect to; protocol can be omitted to use the default `https://`; 
          query parameters can be supplied via the separate `query` input and/or here as `?param1&param2`
        * `query`: a map of query parameters to URL encode and add to the URL
        * `body`: an object to be serialized and sent as the body (or just set as body if it is an array of bytes)
        * `charset`: the character set to use to convert between byte arrays and strings for the request body and response content;
             not applied if `body` is already a byte array, and not applied to the `content_bytes` output;
             defaults to the system default
        * `status_code`: the word `any`, a number, or a predicate DSL expression to require of the response status code,
             defaulting to `{ less-than: 400, greater-than-or-equal-to: 200 }`
        * `headers`: a map of header key-values to set on the request
        * `method`: the HTTP method for the request, defaulting to `get`
        * `username` and `password`: credentials to set on the request, e.g. for Basic auth
             (other auth schemes can be implemented using `headers`)
        * `config`: allows configuration of HTTPS, specifically a map of booleans `laxRedirect`, `trustAll`, and `trustSelfSigned`;
             defaults to entity config or `brooklyn.properties` values of the same keys prefixed with
             `brooklyn.https.config.`, and otherwise defaulting to `false` for each for security;
             this allows e.g. configuration to work with self-signed hosts where the network is trusted
      output: |
        * `status_code`: integer status code, e.g. 200
        * `headers`: a map of header keys to a _list_ of values for that header on the response (as multiple values are permitted)
        * `content`: the content, converted to a string using `charset`
        * `content_bytes`: the content, as a raw byte array
        * `content_json`: the content, parsed as JSON if possible to return a map, list, string, or primitive (null if not valid json)
        * `duration`: how long the request took

    - name: shell
      summary: Runs a command using the local system shell
      shorthand: '`shell COMMAND`'
      input: |
        * `command`: the command to run
        * `env`: a map of string keys with values whose JSON is taken and passed to the command be executed
        * `exit_code`: the word `any`, a number, or a predicate DSL expression (e.g. `less-than: 32`)
        * `output_max_size`: by default output from commands is limited to 100k, truncating to keep the end; this can be increased or decreased,
          or disabled with `-1` (assuming Apache Brooklyn has sufficient memory to hold the data)
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred
      output: |
        * `stdout`
        * `stderr`
        * `exit_code`

    - name: ssh
      summary: Runs a command over ssh
      shorthand: '`ssh COMMAND`'
      input: |
        * `command`: the command to run
        * `env`: a map of string keys with values whose JSON is taken and passed to the command be executed
        * `exit_code`: the word `any`, a number, or a predicate DSL expression (e.g. `less-than: 32`)
        * `output_max_size`: by default output from commands is limited to 100k, truncating to keep the end; this can be increased or decreased,
          or disabled with `-1` (assuming Apache Brooklyn has sufficient memory to hold the data)
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred

      #  [//]: # (* `endpoint`: an alternative endpoint &#40;format TODO&#41;; typically this is omitted and the SSH machine location of the entity is the target)
      #  [//]: # (* `key`: a private key to use for the connection to the endpoint &#40;TODO, again typically embedded in the SSH machine location of the entity&#41;)
      output: |
        * `stdout`
        * `stderr`
        * `exit_code`

    - name: winrm
      summary: Runs a command over winrm
      shorthand: '`winrm COMMAND`'
      input: |
        * `command`: the command to run
        * `env`: a map of string keys with values whose JSON is taken and passed to the command be executed
        * `exit_code`: the word `any`, a number, or a predicate DSL expression (e.g. `less-than: 32`)
                         to require of the exit status code from the command, defaulting to `0`
        * `output_max_size`: by default output from commands is limited to 100k, truncating to keep the end; this can be increased or decreased,
          or disabled with `-1` (assuming Apache Brooklyn has sufficient memory to hold the data)
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred

      #  [//]: # (* `endpoint`: an alternative endpoint &#40;format TODO&#41;; typically this is omitted and the SSH machine location of the entity is the target)
      #  [//]: # (* `key`: a private key to use for the connection to the endpoint &#40;TODO, again typically embedded in the SSH machine location of the entity&#41;)

      output: |
        * `stdout`
        * `stderr`
        * `exit_code`


    - name: ansible-ssh
      summary: |
        Runs a playbook via Ansible by SSHing to the VM under management.
        These playbooks will often target `localhost` but may target other servers.
      shorthand: '`ansible-ssh PLAYBOOK_NAME [ "from" PLAYBOOK_URL ]`'
      input: |
        * `playbook_name`: name of the playbook to run; required
        * `playbook_url`: URL for downloading the playbook; exactly one of this or `playbook_yaml` is required
        * `playbook_yaml`: YAML for the playbook, embedded in the step
        * `vars`: optional variables to pass to Ansible
        * `run_dir`: the directory on the target system where playbooks should be installed and run;
             defaults to an entity-specific folder
        * `install`: whether to install Ansible if necessary, defaults to `true`
        * `install_dir`: the directory on the target system from which Ansible should be downloaded and installed,
             if `install` is not false, defaults to an entity-specific folder
        * `env`: a map of string keys with values whose JSON is taken and passed to the command be executed
        * `exit_code`: the word `any`, a number, or a predicate DSL expression (e.g. `less-than: 32`)
                         to require of the exit status code from the command, defaulting to `0`
        * `output_max_size`: by default output from commands is limited to 100k, truncating to keep the end; this can be increased or decreased,
          or disabled with `-1` (assuming Apache Brooklyn has sufficient memory to hold the data)
      output: |
        * `stdout`
        * `stderr`
        * `exit_code`

-
  section_name: Application Models
  section_intro: |
    The next group of step types manipulate the application models in Brooklyn.

  steps:
    - name: invoke-effector
      summary: Invokes an effector
      shorthand: '`invoke-effector EFFECTOR [ "on" ENTITY ] [ "with" ARGS ]`'
      input: |
        * `effector`: the name of the effector to invoke
        * `entity`: optional entity or entity ID where the effector should be invoked,
          if not the entity where the workflow is running
        * `args`: map of argument names to values to pass to the effector;
          if supplied as shorthand, it expects the form `arg1=v1, arg2=v2`;
          complex arguments are recommended to be passed using longhand syntax
      output: the returned object from the invoked effector

    - name: set-config
      summary: Sets the value of a config key on an entity
      shorthand: '`set-config [TYPE] CONFIG_KEY_NAME = VALUE`'
      input: |
        * `config`: either a string, being the config key name, or a map, containing the `name` and 
          optionally the `type` (defaulting to the declared type of the config key, if present, or to `Object`)
          and/or the `entity` where the config should be set (defaulting to the entity where the workflow is running);
          the value will be coerced to the given type, e.g. to force conversion to an integer or to a bean registered type
        * `value`: the value to set
      output: the output from the previous step, or null if this is the first step

    - name: set-sensor
      summary: Sets the value of a sensor on an entity
      shorthand: '`set-sensor [TYPE] SENSOR_NAME = VALUE`'
      input: |
        * `sensor`: either a string, being the sensor name, or a map, containing the `name` and 
          optionally the `type` (defaulting to the declared type of the sensor, if present, or to `Object`)
          and/or the `entity` where the sensor should be set (defaulting to the entity where the workflow is running);
          the value will be coerced to the given type, e.g. to force conversion to an integer or to a bean registered type;
          if the `name` contains bracketed portions, these are treated as keys in a map or positions in list to be set
        * `value`: the value to set
        * `require`: a condition to evaluate against the sensor value, 
          evaluated along with `value` while holding the lock on the sensor;
          if the condition is not satisfied, an error is thrown and the sensor is not set,
          enabling lightweight atomic check-and-set operations against the current value 
          without needing to use a explicit `lock` in the workflow
      output: the output from the previous step, or null if this is the first step

    - name: clear-config
      summary: Clears the value of a config key on an entity
      shorthand: '`clear-config [TYPE] CONFIG_KEY_NAME`'
      input: |
        * `config`: a string being the config key name or a map containing the `name` and
          optionally the `entity` where the config should be set (defaulting to the entity where the workflow is running)
      output: |
        the output from the previous step, or null if this is the first step

    - name: clear-sensor
      summary: Clears the value of a sensor on an entity
      shorthand: '`clear-sensor [TYPE] SENSOR_NAME`'
      input: |
        * `sensor`: a string being the sensor name or a map containing the `name` and
          optionally the `entity` where the sensor should be cleared (defaulting to the entity where the workflow is running);
          as with `set-sensor`, the `name` can include bracketed portions to remove an entry from a map or list
      output: the output from the previous step, or null if this is the first step

    - name: deploy-application
      summary: Deploys an application from a registered type or a blueprint
      shorthand: '`deploy-application [TYPE]`'
      input: |
        * `type`: a registered type to deploy as a root application, without any configuration; exactly one of this and `blueprint` is required
        * `blueprint`: the blueprint to deploy
        * `format`: optional format specifier for the blueprint
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred
      output: |
        * `app`: the application instance deployed

    - name: add-entity
      summary: Deploys an application from a registered type or a blueprint
      shorthand: '`add-entity [TYPE]`'
      input: |
        * `type`: a registered type to add as a child here, without any configuration; exactly one of this and `blueprint` is required
        * `blueprint`: the blueprint of the entity, or a list of such blueprints, to be added at the current entity
        * `format`: optional format specifier for the blueprint
        * `start`: optional specifier for whether and how the entity should be started, if the current entity is running, either `async` in background (default), `sync` in foreground, or `disabled` to never start
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred
      output: |
        * `entities`: a list of entities added
        * `entity`: if a single entity was added, that entity, otherwise unset

    - name: delete-entity
      summary: Deploys an application from a registered type or a blueprint
      shorthand: '`delete-entity ENTITY`'
      input: |
        * `entity`: the entity or entity ID to delete (unmanage)
      output: the output from the previous step, or null if this is the first step

    - name: reparent-entity
      summary: Moves an entity to be under a new parent
      shorthand: '`reparent-entity child CHILD under PARENT`'
      input: |
        * `child`: the entity or entity ID to be moved
        * `parent`: the entity or entity ID of the new parent
      output: the output from the previous step, or null if this is the first step

    - name: set-entity-name
      summary: Sets the display name of an entity
      shorthand: '`set-entity-name VALUE`'
      input: |
        * `value`: the new name
        * `entity`: the entity on which to set (default as the entity where the workflow is running)
      output: the output from the previous step, or null if this is the first step

    - name: apply-initializer
      summary: Moves an entity to be under a new parent
      shorthand: '`apply-initializer [TYPE [at ENTITY]]`'
      input: |
        * `type`: a registered type to add as a child here, without any configuration; exactly one of this and `blueprint` is required
        * `blueprint`: the blueprint of the entity, or a list of such blueprints, to be added at the current entity
        * `entity`: the entity or entity ID where the initializer should be run, defaulting to the current entity
        * `interpolation_mode` and `interpolation_errors`: as per `let` and `load`, with defaults `full` and `ignore` so that applicable values are resolved and others deferred
      output: the output from the previous step, or null if this is the first step

    - name: add-policy
      summary: Adds a policy or other adjunct from a registered type or a blueprint
      shorthand: '`add-policy [TYPE] [at ENTITY] [unique-tag UNIQUE_TAG]`'
      input: |
        * `type`: a registered type for a policy or other adjunct, to be attached to this entity, without any configuration; exactly one of this and `blueprint` is required
        * `blueprint`: the blueprint for a policy, enricher, feed, or other adjunct, to be added at the current entity
        * `entity`: the entity or entity ID where the adjunct should be added, defaulting to the current entity
        * `unique-tag`: a tag to uniquely identify this adjunct, defaulting to the workflow step; this will replace any instance with the same unique-tag at the entity
          (to make the operation idempotent)
      output: |
        * `adjunct`: the policy or other adjunct added
        * `policy` or `enricher` or `feed`: as above, depending on the type of the adjunct

    - name: delete-policy
      summary: Removes a policy or other adjunct at an entity
      shorthand: '`delete-policy POLICY [at ENTITY]`'
      input: |
        * `policy`: the policy or adjunct to be removed, or the unique tag or ID of such an adjunct
        * `entity`: the entity or entity ID where the adjunct should be removed, defaulting to the current entity
      output: the output from the previous step, or null if this is the first step

    - name: update-children
      summary: Updates children of an entity to be in 1:1 correspondence with items in a given list
      shorthand: '`update-children [of PARENT] type BLUEPRINT id IDENTIFIER_EXPRESSION from ITEMS`'
      input: |
        * `blueprint`: a blueprint or name of a registered entity type to use to create the children;
          this is required unless `on_create` is specified; where supplied as a blueprint (not a string)
          the variable `item` can be referenced to provide initial values, but note this is not updated
        * `identifier_expression`: an expression in terms of a local variable `item` to use to identify the same child;
          e.g. if the `items` is of the form `[{ field_id: 1, name: "Ticket 1" },...]` then
          `identifier_expression: ticket_${item.field_id}` will create/update/delete a child whose ID is `ticket_1`;
          used only by the default `match_check` so not required if that is overridden not to use it
        * `items`: the list of items to be used to create/update/delete the children  
        * `parent`: the entity or entity ID whose children are to be updated, defaulting to the current entity;
          any children which do not match something in `items` may be removed
        
        * `on_create`: an optionally supplied workflow to run at any newly created child, where no pre-existing child was found
          corresponding to an item in `items` and `update-children` created it from `blueprint`,
          passed the `item` (and all inputs to the `update-children` step) 
          and typically doing initial setup as required on the child;
          the default behavior is to invoke an `on_create` effector at the child (if there is such an effector, otherwise do nothing), passing `item`;
          this is invoked prior to `on_update` so if there is nothing special needed for creation this can be omitted
        
        * `on_update`: a workflow to run for each child which has a corresponding item,
          run in the context of the parent, passed the `child` and `item` and `index` 
          (and all inputs to the `update-children` step), 
          and typically updating config and sensors as appropriate on the child, possibly in a sub-workflow in `target: child`;
          the default behavior is to invoke an `on_update` effector at the child (if there is such an effector, otherwise do nothing);
          if the name or any policies may need to change on update, that should be considered in this workflow;
          if the `update-children` is performed frequently, it might be efficient in this method to check whether the `item` has changed
        * `on_update_child`: as `on_update`, but run in the context of the `child` entity for easier configuration there,
          still with access to `item` and `index`
        
        * `on_delete`: a workflow to run on each child which no longer has a corresponding item prior to its being deleted;
          the default behavior is to invoke an `on_delete` effector at the child (if there is such an effector, or nothing);
          if this workflow returns `false` the framework will not delete it;
          this workflow may reparent or delete the entity, although if deletion is desired there is no need as that will be done after this workflow
        
        * `match_check`:
          this optionally supplied workflow allows the matching process to be customized, filtering and determining the intended child or its id; 
          it will be invoked for each item in `items` to find a matching child/entity if one is already present;
          the workflow is passed input variable `item` (and other inputs to the `update-children` step)
          and should return either the entity that corresponds to it which should be updated (`on_update`) or the identifier for the child that should be created,
          or `null` if the item should be omitted;
          the default implementation is to evaluate the expression in `identifier`, i.e. `let id = ${${identifier}}`,
          then to return any child matching that if there is one or the resolved identifier, i.e. `${parent.child[${id}]} ?? ${id}`;
          this workflow may create or reparent an entity and return it, and it will not have `on_create` invoked
        
        * `creation_check`:
          this optionally supplied workflow allows filtering and custom creation;
          it will be invoked for each item in `items` for which the `match_check` returned a string value indicating to create a child,
          the workflow is passed the resulting `match` and the `item` (and other inputs to the `update-children` step),
          and should return the entity created or `null` if the item should be omitted;
          the result of this, if not null, will have the `on_create` handler invoked on it;
          the default implementation is to create the entity (applying the `resolve_expression` transform on `blueprint`),
          set the ID, and return the newly created entity
        
        * `deletion_check`:
          this optionally supplied workflow allows customizing pre-deletion activities and/or the deletion itself;
          it will be invoked for each child of `parent` which was not returned by the `match_check` or `creation_check`, 
          with each such entity passed as an input variable `child` (along with other inputs to the `update-children` step);
          it can then return `true` or `false` to specify whether the child should be deleted
          (with `on_delete` called prior to deletion if `true` is returned);
          this workflow may reparent the entity and return `false` if it is intended to keep the entity but
          disconnect it from this synchronization process,
          or may even `delete-entity ${child}` (although that is not usually necessary)

      output: the output from the previous step, or null if this is the first step

-
  section_name: General Purpose
  section_intro: |
    Miscellaneous step types that don't fit into the other categories.
  steps:

    - name: log
      summary: Logs a message
      shorthand: '`log MESSAGE`'
      input: |
        * `message`: the message to be logged
        * `level`: the log level (default `info`)
        * `category`: the log category (default `o.a.b.core.workflow.steps.flow.LogWorkflowStep`)
      output: the output from the previous step, or null if this is the first step

    - name: no-op
      summary: |
        Does nothing. It is mainly useful when setting a `next` point to jump to,
        optionally with a `condition`.
      shorthand: '`no-op`'
      input: _none_
      output: the output from the previous step, or null if this is the first step

    - name: sleep
      summary: Causes execution to pause for a specified duration
      shorthand: '`sleep DURATION`'
      input: |
        * `duration`: how long to sleep for, e.g. `5s` for 5 seconds
      output: the output from the previous step, or null if this is the first step



