Workflow Instance

The Workflow Instance component serves as a bridge between older v1.* manifests and component-based v2 manifests. To fully support reconfiguration, the component can also respond to changes to certain parameters via triggers.

External Dependencies

The component requires a workflow.Service instance to be present in the environment. Secure vault is used by the workflow.Service automatically if present in the environment. If your manifest requires certain properties or markers, you must explicitly bind them on the corresponding interfaces:

application:
  interfaces:
    output:
      red: "bind(workflow#result.red)"
      green: "bind(workflow#result.green)"
  components:
    workflow:
      type: workflow.Instance
      interfaces:
        result:
          red: publish-signal(string)
          green: publish-signal(int)
        has-internet-access:
          has-internet-access: consume-signal(unit)
        properties:
          sample-property-red: consume-signal(string)
          sample-property-green: consume-signal(int)
      required: [has-internet-access, properties]
      configuration:
        configuration.workflows:
          launch:
            steps:
              - get-env-props:
                  action: getEnvironmentProperties
                  output:
                    props: result
            return:
              red:
                value: "{$.props.properties.sample-property-red}"
              green:
                value: "{$.props.properties.sample-property-green}"
          destroy:
            steps: []
    marker:
      type: reference.Service
      interfaces:
        has-internet-access:
          has-internet-access: publish-signal(unit)
    props:
      type: reference.Service
      interfaces:
        properties:
          sample-property-red: publish-signal(string)
          sample-property-green: publish-signal(int)
  bindings:
      - [workflow, marker]
      - [workflow, props]

Configuration Format

The component configuration contains following sections:

Input Parameters Defaults

Input parameter defaults are written in the form of input.<parameter name>: <default value>

Example:

input.app-tier-size: 1
input.app-fork:      master
input.app-branch:    HEAD

Each of these values should correspond to a configuration pin on the input interface (see below).

Triggers

configuration.triggers is a configuration section that defines how the component will respond to updates to its parameters. The body of the section is a mapping: <interface name>.<parameter>: <workflow name>

configuration.triggers:
  input.param-job-name: handler-workflow-name
  properties.other-signal: other-workflow-name

Since version 37, using a wildcard instead of a pin name will fire the trigger if any of the interface pins or its status changed:

configuration.triggers:
properties.*: handler-workflow-name

Each worklfow that serves as a handler must be able to run without specifying any parameters. Once a trigger fires, its handler is put into the component’s internal queue. Any subsequent attempts to enqueue the same workflow will be ignored until it starts executing. If the workflow uses instances variables, they will be evaluated at the time of execution. We recommend that authors:

  • Make handlers idempotent so that subsequent executions do not cause issues
  • Not assume that the change in properties that triggered a workflow is the only one that happens before it starts executing.

Note

Adding a parameter with a trigger during recofiguratoin will cause immediate trigger execution.

Recurring Workflows

configuration.schedule configuration section allows to trigger workflows on a regular basis. The body of the section is a mapping from workflow name to a cron expression:

configuration.schedule:
  health-check-workflow: "0 0/5 * * * ? *" # run every 5 minutes

For the full documentation on the cron syntax visit http://quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger. All handler workflow requirements decribed in the Triggers section apply to recurring workflows as well.

Note

This feature is not related to an “Execute in” field available on the “Аdvanced Launch” screen of instance jobs. The typical uses of recurring workflows are updating instance signals and health checks. The typical uses of scheduled jobs are deferred destroy and planned maintenance.

Note

Adding a parameter with a trigger during recofiguratoin will cause immediate trigger execution.

Workflows

configuration.workflows is where you put your v1 manifest body.

Exposed Status

Workflow execution can cause the component to change its status. For example, failure to restore a backup must be obvious to a component’s peers and human users. On the other hand, some workflows do not affect operability of their component: if a workflow attempts to acquire an item from an empty pool, the error should be returned in the command response, but the workflow.Instance should stay up and running.

Use the configuration.propagateStatus list to enumerate workflows — apart from launch and destroy — that should affect instance status.

Since Platform v37, it is possible to control which statuses of specific workflows will affect instance status. Available workflow statuses are progress and result, by default both statuses are propagated. If progress is exposed, then the instance will move to Executing state, when the workflow is running. If result is exposed, then workflow failure will move instance to Failed state, or change it to Active, when the command is called on a failed instance.

Example usage:

configuration.propagateStatus:
  - my-workflow-1
  - my-workflow-2: [progress, result]
  - my-workflow-3: [result]

Interfaces

There are no static interfaces in the workflow.Instance component. You must obey the following rules when defining dynamic interfaces on a component:

  • The input interface, if present, must contain configuration pins that are used to intialize intance properties. Unlike v1 manifests, you don’t need to redeclare these as parameters of the launch workflow. But if you are declaring parameters in the parameters section, you should declare all input pins as parameters, otherwise there will be a validation warning.
  • Launch workflow return values are mapped to publish-signal pins in the result interface. Make sure the types match or you’ll get a runtime error. Other workflows can update these signals, but cannot define new signals.
  • Exposed commands should correspond to instance workflows and parameters should match if they are provided in the parameters section.

The only pin types availabe to dynamic interfaces are the following:

  • configuration - Launch parameters, input interface only
  • consume-signal — To be set by other components, will be available via getSignals call
  • publish-signal — These can only occur in the result interface
  • send-command — Used to declare commands you expect to run on external services
  • receive-command — These expose instance workflows to the portal and other components, only allowed in the actions interface.

Example

application:
  configuration:
    input.app-fork:                      qubell
    input.app-branch:                    HEAD
  interfaces:
    input:
      app-fork: "bind(workflow#input.app-fork)"
      app-branch: "bind(workflow#input.app-branch)"
    output:
      db-hosts: "bind(workflow#result.db-hosts)"
  components:
    workflow:
      type: workflow.Instance
      interfaces:
        input:   # these are the input parameters that will be available to all workflows within the component
          app-fork: configuration(string)
          app-branch: configuration(string)
        result:  # these are the return values that will be visible on the UI and available for using by other components
          db-hosts: publish-signal(list<string>)
      configuration:
        # This is the place to put your workflows, note that launch parameters have been moved to the input interface and
        # configuration
        configuration.workflows:
          launch:
            steps:
              - db-tier:
                  action: .compute.grow
                  parameters:
                    roleName: db-node
                  output:
                    db-hosts: ips
              - deploy-db:
                  action: .deploy.db
                  precedingPhases: [db-tier]
                  parameters:
                    app-fork: "{$.app-fork}"
                    app-branch: "{$.app-branch}"
            return:
              db-hosts:
                value: ${db-hosts}
          .compute.grow:
            parameters:
              - roleName:
                  default: defaultRole
              - quantity:
                  default: 1
              - identity:
                  default: undefined
              - credential:
                  default: undefined
            steps:
              provision-vms:
                action: provisionVms
                parameters:
                  roleName: "{$.roleName}"
                  hardwareId: m1.small
                  quantity: "{$.quantity}"
                  retryCount: 1
                  jcloudsProvider: aws-ec2
                  jcloudsEndpoint: https://ec2.us-east-1.amazonaws.com
                  jcloudsRegions: us-east-1
                  jcloudsNodeNamePrefix: petclinic
                  jcloudsIdentity: ${identity}
                  jcloudsCredential: ${credential}
                  vmIdentity: ubuntu
                  imageId: us-east-1/ami-967edcff
                  securityGroup: default
                output:
                  hosts: ips
            return:
              ips:
                value: ${hosts}
          .deploy.db:
            parameters:
              - app-fork:
                  default: undefined
              - app-branch:
                  default: undefined
            steps:
              - install-db:
                  action: chefrun
                  parameters:
                    roles: [ db-node ]
                    runList: ["recipe[mysql::server]"]
                    isSolo: true
                    recipeUrl: "https://github.com/qubell/cookbooks/raw/0.2/cookbooks.tar.gz"
                    jattrs:
                      mysql:
                        server_root_password: e653e94ee8d064ec95ef5a1381c87a23
                        server_repl_password: e653e94ee8d064ec95ef5a1381c87a23
                        server_debian_password: e653e94ee8d064ec95ef5a1381c87a23
              - deploy-db:
                  action: chefrun
                  precedingPhases: [ install-db ]
                  parameters:
                    roles: [ db-node ]
                    runList: ["recipe[qubell::mysql]"]
                    isSolo: true
                    recipeUrl: "https://github.com/qubell/cookbooks/raw/0.2/cookbooks.tar.gz"
                    jattrs:
                        database:
                          name: "petclinic"
                          user: "jpetstore"
                          password: "jpetstore"
                          schema: "https://raw.github.com/{$.app-fork}/starter-java-web/{$.app-branch}/src/main/resources/db/mysql/petclinic-mysql-schema.sql"
                          data:   "https://raw.github.com/{$.app-fork}/starter-java-web/{$.app-branch}/src/main/resources/db/mysql/petclinic-mysql-dataload.sql"
          destroy:
            steps:
                - destroy:
                    action: undeployEnv