AWSCDK::StepFunctions

135 types

AWS Step Functions Construct Library

The aws-cdk-lib/aws-stepfunctions package contains constructs for building serverless workflows using objects. Use this in conjunction with the aws-cdk-lib/aws-stepfunctions-tasks package, which contains classes used to call other AWS services.

Defining a workflow looks like this (for the Step Functions Job Poller example):

State Machine

A stepfunctions.StateMachine is a resource that takes a state machine definition. The definition is specified by its start state, and encompasses all states reachable from the start state:

start_state = AWSCDK::StepFunctions::Pass.jsonata(self, "StartState")

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(start_state),
})

State machines are made up of a sequence of Steps, which represent different actions taken in sequence. Some of these steps represent control flow (like Choice, Map and Wait) while others represent calls made against other AWS services (like LambdaInvoke). The second category are called Tasks and they can all be found in the module aws-stepfunctions-tasks.

State machines execute using an IAM Role, which will automatically have all permissions added that are required to make all state machine tasks execute properly (for example, permissions to invoke any Lambda functions you add to your workflow). A role will be created by default, but you can supply an existing one as well.

Set the removal_policy prop to RemovalPolicy.RETAIN if you want to retain the execution history when CloudFormation deletes your state machine.

Alternatively you can specify an existing step functions definition by providing a string or a file that contains the ASL JSON.

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachineFromString", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_string("{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}"),
})

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachineFromFile", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_file("./asl.json"),
})

Query Language

Step Functions now provides the ability to select the QueryLanguage for the state machine or its states: JSONata or JSONPath.

For new state machines, we recommend using JSONata by specifying it at the state machine level. If you do not specify a QueryLanguage, the state machine will default to using JSONPath.

If a state contains a specified QueryLanguage, Step Functions will use the specified query language for that state. If the state does not specify a QueryLanguage, then it will use the query language specified in the state machine level, or JSONPath if none.

If the state machine level QueryLanguage is set to JSONPath, then any individual state-level QueryLanguage can be set to either JSONPath or JSONata to support incremental upgrades. If the state-level QueryLanguage is set to JSONata, then any individual state-level QueryLanguage can either be JSONata or not set.

jsonata = AWSCDK::StepFunctions::Pass.jsonata(self, "JSONata")
json_path = AWSCDK::StepFunctions::Pass.json_path(self, "JSONPath")
definition = jsonata._next(json_path)

AWSCDK::StepFunctions::StateMachine.new(self, "MixedStateMachine", {
    # queryLanguage: sfn.QueryLanguage.JSON_PATH, // default
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# This throws an error. If JSONata is specified at the top level, JSONPath cannot be used in the state machine definition.
AWSCDK::StepFunctions::StateMachine.new(self, "JSONataOnlyStateMachine", {
    query_language: AWSCDK::StepFunctions::QueryLanguage::JSONATA,
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

The AWS CDK defines state constructs, and there are 3 ways to initialize them.

Method Query Language Description
State.jsonata() JSONata Use this method to specify a state definition using JSONata only fields.
State.jsonPath() JSONPath Use this method to specify a state definition using JSONPath only fields.
new State() JSONata or JSONPath This is a legacy pattern. Since fields for both JSONata and JSONPath can be used, it is recommended to use State.jsonata() or State.jsonPath() for better type safety and clarity.

Code examples for initializing a Pass State with each pattern are shown below.

# JSONata Pattern
AWSCDK::StepFunctions::Pass.jsonata(self, "JSONata Pattern", {
    outputs: {foo: "bar"},
})

# JSONPath Pattern
AWSCDK::StepFunctions::Pass.json_path(self, "JSONPath Pattern", {
    # The outputs does not exist in the props type
    # outputs: { foo: 'bar' },
    output_path: "$.status",
})

# Constructor (Legacy) Pattern
AWSCDK::StepFunctions::Pass.new(self, "Constructor Pattern", {
    query_language: AWSCDK::StepFunctions::QueryLanguage::JSONATA,
     # or JSON_PATH
    # Both outputs and outputPath exist as prop types.
    outputs: {foo: "bar"},
     # For JSONata
    # or
    output_path: "$.status",
})

Learn more in the blog post Simplifying developer experience with variables and JSONata in AWS Step Functions.

JSONata example

The following example defines a state machine in JSONata that calls a fictional service API to update issue labels.

require 'aws-cdk-lib'
connection = nil # AWSCDK::Events::Connection


get_issue = AWSCDK::StepFunctionsTasks::HttpInvoke.jsonata(self, "Get Issue", {
    connection: connection,
    api_root: "{% 'https://' & $states.input.hostname %}",
    api_endpoint: AWSCDK::StepFunctions::TaskInput.from_text("{% 'issues/' & $states.input.issue.id %}"),
    method: AWSCDK::StepFunctions::TaskInput.from_text("GET"),
    # Parse the API call result to object and set to the variables
    assign: {
        hostname: "{% $states.input.hostname %}",
        issue: "{% $parse($states.result.ResponseBody) %}",
    },
})

update_labels = AWSCDK::StepFunctionsTasks::HttpInvoke.jsonata(self, "Update Issue Labels", {
    connection: connection,
    api_root: "{% 'https://' & $states.input.hostname %}",
    api_endpoint: AWSCDK::StepFunctions::TaskInput.from_text("{% 'issues/' & $states.input.issue.id & 'labels' %}"),
    method: AWSCDK::StepFunctions::TaskInput.from_text("POST"),
    body: AWSCDK::StepFunctions::TaskInput.from_object({
        labels: "{% [$type, $component] %}",
    }),
})
not_match_title_template = AWSCDK::StepFunctions::Pass.jsonata(self, "Not Match Title Template")

definition = get_issue._next(AWSCDK::StepFunctions::Choice.jsonata(self, "Match Title Template?")._when(AWSCDK::StepFunctions::Condition.jsonata("{% $contains($issue.title, /(feat)|(fix)|(chore)(w*):.*/) %}"), update_labels, {
    assign: {
        type: "{% $match($states.input.title, /(w*)((.*))/).groups[0] %}",
        component: "{% $match($states.input.title, /(w*)((.*))/).groups[1] %}",
    },
}).otherwise(not_match_title_template))

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
    timeout: AWSCDK::Duration.minutes(5),
    comment: "automate issue labeling state machine",
})

JSONPath (Legacy pattern) example

Defining a workflow looks like this (for the Step Functions Job Poller example):

require 'aws-cdk-lib'

submit_lambda = nil # AWSCDK::Lambda::Function
get_status_lambda = nil # AWSCDK::Lambda::Function


submit_job = AWSCDK::StepFunctionsTasks::LambdaInvoke.new(self, "Submit Job", {
    lambda_function: submit_lambda,
    # Lambda's result is in the attribute `guid`
    output_path: "$.guid",
})

wait_x = AWSCDK::StepFunctions::Wait.new(self, "Wait X Seconds", {
    time: AWSCDK::StepFunctions::WaitTime.seconds_path("$.waitSeconds"),
})

get_status = AWSCDK::StepFunctionsTasks::LambdaInvoke.new(self, "Get Job Status", {
    lambda_function: get_status_lambda,
    # Pass just the field named "guid" into the Lambda, put the
    # Lambda's result in a field called "status" in the response
    input_path: "$.guid",
    output_path: "$.status",
})

job_failed = AWSCDK::StepFunctions::Fail.new(self, "Job Failed", {
    cause: "AWS Batch Job Failed",
    error: "DescribeJob returned FAILED",
})

final_status = AWSCDK::StepFunctionsTasks::LambdaInvoke.new(self, "Get Final Job Status", {
    lambda_function: get_status_lambda,
    # Use "guid" field as input
    input_path: "$.guid",
    output_path: "$.Payload",
})

definition = submit_job._next(wait_x)._next(get_status)._next(AWSCDK::StepFunctions::Choice.new(self, "Job Complete?")._when(AWSCDK::StepFunctions::Condition.string_equals("$.status", "FAILED"), job_failed)._when(AWSCDK::StepFunctions::Condition.string_equals("$.status", "SUCCEEDED"), final_status).otherwise(wait_x))

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
    timeout: AWSCDK::Duration.minutes(5),
    comment: "a super cool state machine",
})

You can find more sample snippets and learn more about the service integrations in the aws-cdk-lib/aws-stepfunctions-tasks package.

State Machine Data

With variables and state output, you can pass data between the steps of your workflow.

Using workflow variables, you can store data in a step and retrieve that data in future steps. For example, you could store an API response that contains data you might need later. Conversely, state output can only be used as input to the very next step.

Variable

With workflow variables, you can store data to reference later. For example, Step 1 might store the result from an API request so a part of that request can be re-used later in Step 5.

In the following scenario, the state machine fetches data from an API once. In Step 1, the workflow stores the returned API data (up to 256 KiB per state) in a variable ‘x’ to use in later steps.

Without variables, you would need to pass the data through output from Step 1 to Step 2 to Step 3 to Step 4 to use it in Step 5. What if those intermediate steps do not need the data? Passing data from state to state through outputs and input would be unnecessary effort.

With variables, you can store data and use it in any future step. You can also modify, rearrange, or add steps without disrupting the flow of your data. Given the flexibility of variables, you might only need to use output to return data from Parallel and Map sub-workflows, and at the end of your state machine execution.

(Start)
   ↓
[Step 1]→[Return from API]
   ↓             ↓
[Step 2] [Assign data to $x]
   ↓             │
[Step 3]         │
   ↓             │
[Step 4]         │
   ↓             │
[Step 5]←────────┘
   ↓     Use variable $x
 (End)

Use assign to express the above example in AWS CDK. You can use both JSONata and JSONPath to assign.

require 'aws-cdk-lib'

call_api_func = nil # AWSCDK::Lambda::Function
use_variable_func = nil # AWSCDK::Lambda::Function

step1 = AWSCDK::StepFunctionsTasks::LambdaInvoke.jsonata(self, "Step 1", {
    lambda_function: call_api_func,
    assign: {
        x: "{% $states.result.Payload.x %}",
    },
})
step2 = AWSCDK::StepFunctions::Pass.jsonata(self, "Step 2")
step3 = AWSCDK::StepFunctions::Pass.jsonata(self, "Step 3")
step4 = AWSCDK::StepFunctions::Pass.jsonata(self, "Step 4")
step5 = AWSCDK::StepFunctionsTasks::LambdaInvoke.jsonata(self, "Step 5", {
    lambda_function: use_variable_func,
    payload: AWSCDK::StepFunctions::TaskInput.from_object({
        x: "{% $x %}",
    }),
})

For more details, see the official documentation

State Output

An Execution represents each time the State Machine is run. Every Execution has State Machine Data: a JSON document containing keys and values that is fed into the state machine, gets modified by individual steps as the state machine progresses, and finally is produced as output.

By default, the entire Data object is passed into every state, and the return data of the step becomes new the new Data object. You can change this behavior, but the way you specify it differs depending on the query language you use.

JSONata

To change the default behavior of using a JSONata, supplying values for outputs. When a string in the value of an ASL field, a JSON object field, or a JSON array element is surrounded by {% %} characters, that string will be evaluated as JSONata . Note, the string must start with {% with no leading spaces, and must end with %} with no trailing spaces. Improperly opening or closing the expression will result in a validation error.

The following example uses JSONata expressions for outputs and time.

AWSCDK::StepFunctions::Wait.jsonata(self, "Wait", {
    time: AWSCDK::StepFunctions::WaitTime.timestamp("{% $timestamp %}"),
    outputs: {
        string_argument: "inital-task",
        number_argument: 123,
        boolean_argument: true,
        array_argument: [1, "{% $number %}", 3],
        intrinsic_functions_argument: "{% $join($each($obj, function($v) { $v }), ', ') %}",
    },
})

For a brief introduction and complete JSONata reference, see JSONata.org documentation.

Reserved variable : $states

Step Functions defines a single reserved variable called $states. In JSONata states, the following structures are assigned to $states for use in JSONata expressions:

// Reserved $states variable in JSONata states
const $states = {
  "input":      // Original input to the state
  "result":     // API or sub-workflow's result (if successful)
  "errorOutput":// Error Output in a Catch
  "context":    // Context object
}

The structure is as follows when expressed as a type.

</code>

You can access reserved variables as follows:

AWSCDK::StepFunctions::Pass.jsonata(self, "Pass", {
    outputs: {
        foo: "{% $states.input.foo %}",
    },
})

JSONPath

To change the default behavior of using a JSON path, supplying values for input_path, result_selector, result_path, and output_path.

Manipulating state machine data using inputPath, resultSelector, resultPath and outputPath

These properties impact how each individual step interacts with the state machine data:

Their values should be a string indicating a JSON path into the State Machine Data object (like "$.MyKey"). If absent, the values are treated as if they were "$", which means the entire object.

The following pseudocode shows how AWS Step Functions uses these parameters when executing a step:

// Schematically show how Step Functions evaluates functions.
// [] represents indexing into an object by a using JSON path.

input = state[inputPath]

result = invoke_step(select_parameters(input))

state[resultPath] = result[resultSelector]

state = state[outputPath]

Instead of a JSON path string, each of these paths can also have the special value JsonPath.DISCARD, which causes the corresponding indexing expression to return an empty object ({}). Effectively, that means there will be an empty input object, an empty result object, no effect on the state, or an empty state, respectively.

Some steps (mostly Tasks) have Parameters, which are selected differently. See the next section.

See the official documentation on input and output processing in Step Functions.

Passing Parameters to Tasks

Tasks take parameters, whose values can be taken from the State Machine Data object. For example, your workflow may want to start a CodeBuild with an environment variable that is taken from the State Machine data, or pass part of the State Machine Data into an AWS Lambda Function.

In the original JSON-based states language used by AWS Step Functions, you would add .$ to the end of a key to indicate that a value needs to be interpreted as a JSON path. In the CDK API you do not change the names of any keys. Instead, you pass special values. There are 3 types of task inputs to consider:

For example, to pass the value that's in the data key of OrderId to a Lambda function as you invoke it, use JsonPath.stringAt('$.OrderId'), like so:

require 'aws-cdk-lib'

order_fn = nil # AWSCDK::Lambda::Function


submit_job = AWSCDK::StepFunctionsTasks::LambdaInvoke.new(self, "InvokeOrderProcessor", {
    lambda_function: order_fn,
    payload: AWSCDK::StepFunctions::TaskInput.from_object({
        OrderId: AWSCDK::StepFunctions::JsonPath.string_at("$.OrderId"),
    }),
})

The following methods are available:

Method Purpose
JsonPath.stringAt('$.Field') reference a field, return the type as a string.
JsonPath.listAt('$.Field') reference a field, return the type as a list of strings.
JsonPath.numberAt('$.Field') reference a field, return the type as a number. Use this for functions that expect a number argument.
JsonPath.objectAt('$.Field') reference a field, return the type as an IResolvable. Use this for functions that expect an object argument.
JsonPath.entirePayload reference the entire data object (equivalent to a path of $).
JsonPath.taskToken reference the Task Token, used for integration patterns that need to run for a long time.
JsonPath.executionId reference the Execution Id field of the context object.
JsonPath.executionInput reference the Execution Input object of the context object.
JsonPath.executionName reference the Execution Name field of the context object.
JsonPath.executionRoleArn reference the Execution RoleArn field of the context object.
JsonPath.executionStartTime reference the Execution StartTime field of the context object.
JsonPath.stateEnteredTime reference the State EnteredTime field of the context object.
JsonPath.stateName reference the State Name field of the context object.
JsonPath.stateRetryCount reference the State RetryCount field of the context object.
JsonPath.stateMachineId reference the StateMachine Id field of the context object.
JsonPath.stateMachineName reference the StateMachine Name field of the context object.

You can also call intrinsic functions using the methods on JsonPath:

Method Purpose
JsonPath.array(JsonPath.stringAt('$.Field'), ...) make an array from other elements.
JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4) partition an array.
JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5) determine if a specific value is present in an array.
JsonPath.arrayRange(1, 9, 2) create a new array containing a specific range of elements.
JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5) get a specified index's value in an array.
JsonPath.arrayLength(JsonPath.listAt('$.inputArray')) get the length of an array.
JsonPath.arrayUnique(JsonPath.listAt('$.inputArray')) remove duplicate values from an array.
JsonPath.base64Encode(JsonPath.stringAt('$.input')) encode data based on MIME Base64 encoding scheme.
JsonPath.base64Decode(JsonPath.stringAt('$.base64')) decode data based on MIME Base64 decoding scheme.
JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm')) calculate the hash value of a given input.
JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2')) merge two JSON objects into a single object.
JsonPath.stringToJson(JsonPath.stringAt('$.ObjStr')) parse a JSON string to an object
JsonPath.jsonToString(JsonPath.objectAt('$.Obj')) stringify an object to a JSON string
JsonPath.mathRandom(1, 999) return a random number.
JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step')) return the sum of two numbers.
JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter')) split a string into an array of values.
JsonPath.uuid() return a version 4 universally unique identifier (v4 UUID).
JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value')) insert elements into a format string.

Amazon States Language

This library comes with a set of classes that model the Amazon States Language. The following State classes are supported:

An arbitrary JSON object (specified at execution start) is passed from state to state and transformed during the execution of the workflow. For more information, see the States Language spec.

Task

A Task represents some work that needs to be done. Do not use the Task class directly.

Instead, use one of the classes in the aws-cdk-lib/aws-stepfunctions-tasks module, which provide a much more ergonomic way to integrate with various AWS services.

Pass

A Pass state passes its input to its output, without performing work. Pass states are useful when constructing and debugging state machines.

The following example injects some fixed data into the state machine through the result field. The result field will be added to the input and the result will be passed as the state's output.

# Makes the current JSON state { ..., "subObject": { "hello": "world" } }
pass = AWSCDK::StepFunctions::Pass.new(self, "Add Hello World", {
    result: AWSCDK::StepFunctions::Result.from_object({hello: "world"}),
    result_path: "$.subObject",
})

# Set the next state
next_state = AWSCDK::StepFunctions::Pass.new(self, "NextState")
pass._next(next_state)

When using JSONata, you can use only outputs.

pass = AWSCDK::StepFunctions::Pass.new(self, "Add Hello World", {
    outputs: {
        sub_object: {hello: "world"},
    },
})

The Pass state also supports passing key-value pairs as input. Values can be static, or selected from the input with a path.

The following example filters the greeting field from the state input and also injects a field called other_data.

pass = AWSCDK::StepFunctions::Pass.new(self, "Filter input and inject data", {
    state_name: "my-pass-state",
     # the custom state name for the Pass state, defaults to 'Filter input and inject data' as the state name
    parameters: {
         # input to the pass state
        input: AWSCDK::StepFunctions::JsonPath.string_at("$.input.greeting"),
        other_data: "some-extra-stuff",
    },
})

The object specified in parameters will be the input of the Pass state. Since neither Result nor ResultPath are supplied, the Pass state copies its input through to its output.

Learn more about the Pass state

Wait

A Wait state waits for a given number of seconds, or until the current time hits a particular time. The time to wait may be taken from the execution's JSON state.

# Wait until it's the time mentioned in the state object's "triggerTime"
# field.
wait = AWSCDK::StepFunctions::Wait.new(self, "Wait For Trigger Time", {
    time: AWSCDK::StepFunctions::WaitTime.timestamp_path("$.triggerTime"),
})

# When using JSONata
# const wait = sfn.Wait.jsonata(this, 'Wait For Trigger Time', {
#   time: sfn.WaitTime.timestamp('{% $triggerTime %}'),
# });

# Set the next state
start_the_work = AWSCDK::StepFunctions::Pass.new(self, "StartTheWork")
wait._next(start_the_work)

Choice

A Choice state can take a different path through the workflow based on the values in the execution's JSON state:

choice = AWSCDK::StepFunctions::Choice.new(self, "Did it work?")

# Add conditions with .when()
success_state = AWSCDK::StepFunctions::Pass.new(self, "SuccessState")
failure_state = AWSCDK::StepFunctions::Pass.new(self, "FailureState")
choice._when(AWSCDK::StepFunctions::Condition.string_equals("$.status", "SUCCESS"), success_state)
choice._when(AWSCDK::StepFunctions::Condition.number_greater_than("$.attempts", 5), failure_state)

# When using JSONata
# choice.when(sfn.Condition.jsonata("{% $status = 'SUCCESS'"), successState);
# choice.when(sfn.Condition.jsonata('{% $attempts > 5 %}'), failureState);

# Use .otherwise() to indicate what should be done if none of the conditions match
try_again_state = AWSCDK::StepFunctions::Pass.new(self, "TryAgainState")
choice.otherwise(try_again_state)

If you want to temporarily branch your workflow based on a condition, but have all branches come together and continuing as one (similar to how an if ... then ... else works in a programming language), use the .afterwards() method:

choice = AWSCDK::StepFunctions::Choice.new(self, "What color is it?")
handle_blue_item = AWSCDK::StepFunctions::Pass.new(self, "HandleBlueItem")
handle_red_item = AWSCDK::StepFunctions::Pass.new(self, "HandleRedItem")
handle_other_item_color = AWSCDK::StepFunctions::Pass.new(self, "HanldeOtherItemColor")
choice._when(AWSCDK::StepFunctions::Condition.string_equals("$.color", "BLUE"), handle_blue_item)
choice._when(AWSCDK::StepFunctions::Condition.string_equals("$.color", "RED"), handle_red_item)
choice.otherwise(handle_other_item_color)

# Use .afterwards() to join all possible paths back together and continue
ship_the_item = AWSCDK::StepFunctions::Pass.new(self, "ShipTheItem")
choice.afterwards._next(ship_the_item)

You can add comments to Choice states as well as conditions that use choice.when.

choice = AWSCDK::StepFunctions::Choice.new(self, "What color is it?", {
    comment: "color comment",
})
handle_blue_item = AWSCDK::StepFunctions::Pass.new(self, "HandleBlueItem")
handle_other_item_color = AWSCDK::StepFunctions::Pass.new(self, "HanldeOtherItemColor")
choice._when(AWSCDK::StepFunctions::Condition.string_equals("$.color", "BLUE"), handle_blue_item, {
    comment: "blue item comment",
})
choice.otherwise(handle_other_item_color)

If your Choice doesn't have an otherwise() and none of the conditions match the JSON state, a NoChoiceMatched error will be thrown. Wrap the state machine in a Parallel state if you want to catch and recover from this.

Available Conditions

JSONata

When you're using JSONata, use the jsonata function to specify the condition using a JSONata expression:

AWSCDK::StepFunctions::Condition.jsonata("{% 1+1 = 2 %}") # true
AWSCDK::StepFunctions::Condition.jsonata("{% 1+1 != 3 %}") # true
AWSCDK::StepFunctions::Condition.jsonata("{% 'world' in ['hello', 'world'] %}") # true
AWSCDK::StepFunctions::Condition.jsonata("{% $contains('abracadabra', /a.*a/) %}")

See the JSONata comparison operators to find more operators.

JSONPath

see step function comparison operators

Parallel

A Parallel state executes one or more subworkflows in parallel. It can also be used to catch and recover from errors in subworkflows. The parameters property can be used to transform the input that is passed to each branch of the parallel execution.

parallel = AWSCDK::StepFunctions::Parallel.new(self, "Do the work in parallel")

# Add branches to be executed in parallel
ship_item = AWSCDK::StepFunctions::Pass.new(self, "ShipItem")
send_invoice = AWSCDK::StepFunctions::Pass.new(self, "SendInvoice")
restock = AWSCDK::StepFunctions::Pass.new(self, "Restock")
parallel.branch(ship_item)
parallel.branch(send_invoice)
parallel.branch(restock)

# Retry the whole workflow if something goes wrong with exponential backoff
parallel.add_retry({
    max_attempts: 1,
    max_delay: AWSCDK::Duration.seconds(5),
    jitter_strategy: AWSCDK::StepFunctions::JitterType::FULL,
})

# How to recover from errors
send_failure_notification = AWSCDK::StepFunctions::Pass.new(self, "SendFailureNotification")
parallel.add_catch(send_failure_notification)

# What to do in case everything succeeded
close_order = AWSCDK::StepFunctions::Pass.new(self, "CloseOrder")
parallel._next(close_order)

Succeed

Reaching a Succeed state terminates the state machine execution with a successful status.

success = AWSCDK::StepFunctions::Succeed.new(self, "We did it!")

Fail

Reaching a Fail state terminates the state machine execution with a failure status. The fail state should report the reason for the failure. Failures can be caught by encompassing Parallel states.

fail = AWSCDK::StepFunctions::Fail.new(self, "Fail", {
    error: "WorkflowFailure",
    cause: "Something went wrong",
})

The Fail state also supports returning dynamic values as the error and cause that are selected from the input with a path.

fail = AWSCDK::StepFunctions::Fail.new(self, "Fail", {
    error_path: AWSCDK::StepFunctions::JsonPath.string_at("$.someError"),
    cause_path: AWSCDK::StepFunctions::JsonPath.string_at("$.someCause"),
})

You can also use an intrinsic function that returns a string to specify CausePath and ErrorPath. The available functions include States.Format, States.JsonToString, States.ArrayGetItem, States.Base64Encode, States.Base64Decode, States.Hash, and States.UUID.

fail = AWSCDK::StepFunctions::Fail.new(self, "Fail", {
    error_path: AWSCDK::StepFunctions::JsonPath.format("error: {}.", AWSCDK::StepFunctions::JsonPath.string_at("$.someError")),
    cause_path: "States.Format('cause: {}.', $.someCause)",
})

When you use JSONata, you can use JSONata expression in the error or cause properties.

fail = AWSCDK::StepFunctions::Fail.new(self, "Fail", {
    error: "{% 'error:' & $someError & '.' %}",
    cause: "{% 'cause:' & $someCause & '.' %}",
})

Map

A Map state can be used to run a set of steps for each element of an input array. A Map state will execute the same steps for multiple entries of an array in the state input.

While the Parallel state executes multiple branches of steps using the same input, a Map state will execute the same steps for multiple entries of an array in the state input.

map = AWSCDK::StepFunctions::Map.new(self, "Map State", {
    max_concurrency: 1,
    items_path: AWSCDK::StepFunctions::JsonPath.string_at("$.inputForMap"),
    item_selector: {
        item: AWSCDK::StepFunctions::JsonPath.string_at("$.Map.Item.Value"),
    },
    result_path: "$.mapOutput",
})

# The Map iterator can contain a IChainable, which can be an individual or multiple steps chained together.
# Below example is with a Choice and Pass step
choice = AWSCDK::StepFunctions::Choice.new(self, "Choice")
condition1 = AWSCDK::StepFunctions::Condition.string_equals("$.item.status", "SUCCESS")
step1 = AWSCDK::StepFunctions::Pass.new(self, "Step1")
step2 = AWSCDK::StepFunctions::Pass.new(self, "Step2")
finish = AWSCDK::StepFunctions::Pass.new(self, "Finish")

definition = choice._when(condition1, step1).otherwise(step2).afterwards._next(finish)

map.item_processor(definition)

When using JSONata, the item_selector property in a Map state can be specified in one of two ways. You can provide a valid JSON object containing JSONata expressions for each value:

map = AWSCDK::StepFunctions::Map.new(self, "Map State", {
    max_concurrency: 1,
    item_selector: {
        id: "{% $states.context.Map.Item.Value.id %}",
        status: "{% $states.context.Map.Item.Value.status %}",
    },
})

Alternatively, you can use the jsonata_item_selector field to directly supply a JSONata string that evaluates to a complete JSON object:

map = AWSCDK::StepFunctions::Map.new(self, "Map State", {
    max_concurrency: 1,
    jsonata_item_selector: "{% {\"id\": $states.input.id, \"status\": $states.input.status} %}",
})

When using JSONata, you can also specify the max_concurrency dynamically using a JSONata expression with the jsonata_max_concurrency property. This allows you to determine the concurrency limit based on state input or other runtime values:

map = AWSCDK::StepFunctions::Map.new(self, "Map State", {
    jsonata_max_concurrency: "{% $states.input.maxConcurrency %}",
    item_selector: {
        item: "{% $states.context.Map.Item.Value %}",
    },
})

Note that jsonata_max_concurrency is mutually exclusive with max_concurrency and max_concurrency_path.

To define a distributed Map state set item_processors mode to ProcessorMode.DISTRIBUTED. An execution_type must be specified for the distributed Map workflow.

map = AWSCDK::StepFunctions::Map.new(self, "Map State", {
    max_concurrency: 1,
    items_path: AWSCDK::StepFunctions::JsonPath.string_at("$.inputForMap"),
    item_selector: {
        item: AWSCDK::StepFunctions::JsonPath.string_at("$.Map.Item.Value"),
    },
    result_path: "$.mapOutput",
})

map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass State"), {
    mode: AWSCDK::StepFunctions::ProcessorMode::DISTRIBUTED,
    execution_type: AWSCDK::StepFunctions::ProcessorType::STANDARD,
})

Visit Using Map state in Distributed mode to orchestrate large-scale parallel workloads for more details.

Distributed Map

Step Functions provides a high-concurrency mode for the Map state known as Distributed mode. In this mode, the Map state can accept input from large-scale Amazon S3 data sources. For example, your input can be a JSON or CSV file stored in an Amazon S3 bucket, or a JSON array passed from a previous step in the workflow. A Map state set to Distributed is known as a Distributed Map state. In this mode, the Map state runs each iteration as a child workflow execution, which enables high concurrency of up to 10,000 parallel child workflow executions. Each child workflow execution has its own, separate execution history from that of the parent workflow.

Use the Map state in Distributed mode when you need to orchestrate large-scale parallel workloads that meet any combination of the following conditions:

A DistributedMap state can be used to run a set of steps for each element of an input array with high concurrency. A DistributedMap state will execute the same steps for multiple entries of an array in the state input or from S3 objects.

distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "Distributed Map State", {
    max_concurrency: 1,
    items_path: AWSCDK::StepFunctions::JsonPath.string_at("$.inputForMap"),
})
distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass State"))

DistributedMap supports various input source types to determine a list of objects to iterate over:

  #
  # JSON state input:
  #  [
  #    "item1",
  #    "item2"
  #  ]
  #
  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap")
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))
  #
  # JSON state input:
  #  {
  #    "distributedMapItemList": [
  #      "item1",
  #      "item2"
  #    ]
  #  }
  #
  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
      items_path: "$.distributedMapItemList",
  })
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))
  require 'aws-cdk-lib'


  #
  # Tree view of bucket:
  #  my-bucket
  #  |
  #  +--item1
  #  |
  #  +--otherItem
  #  |
  #  +--item2
  #  |
  #  ...
  #
  bucket = AWSCDK::S3::Bucket.new(self, "Bucket", {
      bucket_name: "my-bucket",
  })

  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
      item_reader: AWSCDK::StepFunctions::S3ObjectsItemReader.new({
          bucket: bucket,
          prefix: "item",
      }),
  })
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))
  #
  # JSON state input:
  #  {
  #    "bucketName": "my-bucket",
  #    "prefix": "item"
  #  }
  #
  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
      item_reader: AWSCDK::StepFunctions::S3ObjectsItemReader.new({
          bucket_name_path: AWSCDK::StepFunctions::JsonPath.string_at("$.bucketName"),
          prefix: AWSCDK::StepFunctions::JsonPath.string_at("$.prefix"),
      }),
  })
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))
  require 'aws-cdk-lib'


  #
  # Tree view of bucket:
  #  my-bucket
  #  |
  #  +--input.json
  #  |
  #  ...
  #
  # File content of input.json:
  #  [
  #    "item1",
  #    "item2"
  #  ]
  #
  bucket = AWSCDK::S3::Bucket.new(self, "Bucket", {
      bucket_name: "my-bucket",
  })

  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
      item_reader: AWSCDK::StepFunctions::S3JsonItemReader.new({
          bucket: bucket,
          key: "input.json",
      }),
  })
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))
  #
  # JSON state input:
  #  {
  #    "bucketName": "my-bucket",
  #    "key": "input.json"
  #  }
  #
  distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
      item_reader: AWSCDK::StepFunctions::S3JsonItemReader.new({
          bucket_name_path: AWSCDK::StepFunctions::JsonPath.string_at("$.bucketName"),
          key: AWSCDK::StepFunctions::JsonPath.string_at("$.key"),
      }),
  })
  distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))

Map states in Distributed mode also support writing results of the iterator to an S3 bucket and optional prefix. Use a ResultWriterV2 object provided via the optional result_writer property to configure which S3 location iterator results will be written. The default behavior id result_writer is omitted is to use the state output payload. However, if the iterator results are larger than the 256 kb limit for Step Functions payloads then the State Machine will fail.

ResultWriterV2 object will default to use the Distributed map's query language. If the Distributed map's does not specify a query language, then it will fall back to the State machine's query langauge.

Note: ResultWriter has been deprecated, use ResultWriterV2 instead. To enable ResultWriterV2, you will have to set the value for '@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2' to true in the CDK context

require 'aws-cdk-lib'


# create a bucket
bucket = AWSCDK::S3::Bucket.new(self, "Bucket")

# create a WriterConfig

distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "Distributed Map State", {
    result_writer_v2: AWSCDK::StepFunctions::ResultWriterV2.new({
        bucket: bucket,
        prefix: "my-prefix",
        writer_config: {
            output_type: AWSCDK::StepFunctions::OutputType::JSONL,
            transformation: AWSCDK::StepFunctions::Transformation::NONE,
        },
    }),
})
distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass State"))
#
# JSON state input:
#  {
#    "bucketName": "my-bucket"
#  }
#
distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
    result_writer_v2: AWSCDK::StepFunctions::ResultWriterV2.new({
        bucket_name_path: AWSCDK::StepFunctions::JsonPath.string_at("$.bucketName"),
    }),
})
distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"))

If you want to specify the execution type for the ItemProcessor in the DistributedMap, you must set the map_execution_type property in the DistributedMap class. When using the DistributedMap class, the ProcessorConfig.executionType property is ignored.

In the following example, the execution type for the ItemProcessor in the DistributedMap is set to EXPRESS based on the value specified for map_execution_type.

distributed_map = AWSCDK::StepFunctions::DistributedMap.new(self, "DistributedMap", {
    map_execution_type: AWSCDK::StepFunctions::StateMachineType::EXPRESS,
})

distributed_map.item_processor(AWSCDK::StepFunctions::Pass.new(self, "Pass"), {
    mode: AWSCDK::StepFunctions::ProcessorMode::DISTRIBUTED,
    execution_type: AWSCDK::StepFunctions::ProcessorType::STANDARD,
})

Custom State

It's possible that the high-level constructs for the states or stepfunctions-tasks do not have the states or service integrations you are looking for. The primary reasons for this lack of functionality are:

If a feature is not available, a CustomState can be used to supply any Amazon States Language JSON-based object as the state definition.

Code Snippets are available and can be plugged in as the state definition.

Custom states can be chained together with any of the other states to create your state machine definition. You will also need to provide any permissions that are required to the role that the State Machine uses.

The Retry and Catch fields are available for error handling. You can configure the Retry field by defining it in the JSON object or by adding it using the add_retry method. However, the Catch field cannot be configured by defining it in the JSON object, so it must be added using the add_catch method.

The following example uses the DynamoDB service integration to insert data into a DynamoDB table.

require 'aws-cdk-lib'


# create a table
table = AWSCDK::DynamoDB::Table.new(self, "montable", {
    partition_key: {
        name: "id",
        type: AWSCDK::DynamoDB::AttributeType::STRING,
    },
})

final_status = AWSCDK::StepFunctions::Pass.new(self, "final step")

# States language JSON to put an item into DynamoDB
# snippet generated from https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-code-snippet.html#tutorial-code-snippet-1
state_json = {
    Type: "Task",
    Resource: "arn:aws:states:::dynamodb:putItem",
    Parameters: {
        TableName: table.table_name,
        Item: {
            id: {
                S: "MyEntry",
            },
        },
    },
    ResultPath: nil,
}

# custom state which represents a task to insert data into DynamoDB
custom = AWSCDK::StepFunctions::CustomState.new(self, "my custom task", {
    state_json: state_json,
})

# catch errors with addCatch
error_handler = AWSCDK::StepFunctions::Pass.new(self, "handle failure")
custom.add_catch(error_handler)

# retry the task if something goes wrong
custom.add_retry({
    errors: [AWSCDK::StepFunctions::Errors.ALL],
    interval: AWSCDK::Duration.seconds(10),
    max_attempts: 5,
})

chain = AWSCDK::StepFunctions::Chain.start(custom)._next(final_status)

sm = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(chain),
    timeout: AWSCDK::Duration.seconds(30),
    comment: "a super cool state machine",
})

# don't forget permissions. You need to assign them
table.grant_write_data(sm)

Task Chaining

To make defining work flows as convenient (and readable in a top-to-bottom way) as writing regular programs, it is possible to chain most methods invocations. In particular, the .next() method can be repeated. The result of a series of .next() calls is called a Chain, and can be used when defining the jump targets of Choice.on or Parallel.branch:

step1 = AWSCDK::StepFunctions::Pass.new(self, "Step1")
step2 = AWSCDK::StepFunctions::Pass.new(self, "Step2")
step3 = AWSCDK::StepFunctions::Pass.new(self, "Step3")
step4 = AWSCDK::StepFunctions::Pass.new(self, "Step4")
step5 = AWSCDK::StepFunctions::Pass.new(self, "Step5")
step6 = AWSCDK::StepFunctions::Pass.new(self, "Step6")
step7 = AWSCDK::StepFunctions::Pass.new(self, "Step7")
step8 = AWSCDK::StepFunctions::Pass.new(self, "Step8")
step9 = AWSCDK::StepFunctions::Pass.new(self, "Step9")
step10 = AWSCDK::StepFunctions::Pass.new(self, "Step10")
choice = AWSCDK::StepFunctions::Choice.new(self, "Choice")
condition1 = AWSCDK::StepFunctions::Condition.string_equals("$.status", "SUCCESS")
parallel = AWSCDK::StepFunctions::Parallel.new(self, "Parallel")
finish = AWSCDK::StepFunctions::Pass.new(self, "Finish")

definition = step1._next(step2)._next(choice._when(condition1, step3._next(step4)._next(step5)).otherwise(step6).afterwards)._next(parallel.branch(step7._next(step8)).branch(step9._next(step10)))._next(finish)

AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

If you don't like the visual look of starting a chain directly off the first step, you can use Chain.start:

step1 = AWSCDK::StepFunctions::Pass.new(self, "Step1")
step2 = AWSCDK::StepFunctions::Pass.new(self, "Step2")
step3 = AWSCDK::StepFunctions::Pass.new(self, "Step3")

definition = AWSCDK::StepFunctions::Chain.start(step1)._next(step2)._next(step3)

Task Credentials

Tasks are executed using the State Machine's execution role. In some cases, e.g. cross-account access, an IAM role can be assumed by the State Machine's execution role to provide access to the resource. This can be achieved by providing the optional credentials property which allows using a fixed role or a json expression to resolve the role at runtime from the task's inputs.

require 'aws-cdk-lib'

submit_lambda = nil # AWSCDK::Lambda::Function
iam_role = nil # AWSCDK::IAM::Role


# use a fixed role for all task invocations
role = AWSCDK::StepFunctions::TaskRole.from_role(iam_role)
# or use a json expression to resolve the role at runtime based on task inputs
# const role = sfn.TaskRole.fromRoleArnJsonPath('$.RoleArn');

submit_job = AWSCDK::StepFunctionsTasks::LambdaInvoke.new(self, "Submit Job", {
    lambda_function: submit_lambda,
    output_path: "$.Payload",
    # use credentials
    credentials: {role: role},
})

See the AWS documentation to learn more about AWS Step Functions support for accessing resources in other AWS accounts.

Service Integration Patterns

AWS Step functions integrate directly with other services, either through an optimised integration pattern, or through the AWS SDK. Therefore, it is possible to change the integration_pattern of services, to enable additional functionality of the said AWS Service:

require 'aws-cdk-aws-glue-alpha'

submit_glue = nil


submit_job = AWSCDK::StepFunctionsTasks::GlueStartJobRun.new(self, "Submit Job", {
    glue_job_name: submit_glue.job_name,
    integration_pattern: AWSCDK::StepFunctions::IntegrationPattern::RUN_JOB,
})

State Machine Fragments

It is possible to define reusable (or abstracted) mini-state machines by defining a construct that implements IChainable, which requires you to define two fields:

Since states will be named after their construct IDs, you may need to prefix the IDs of states if you plan to instantiate the same state machine fragment multiples times (otherwise all states in every instantiation would have the same name).

The class StateMachineFragment contains some helper functions (like prefix_states()) to make it easier for you to do this. If you define your state machine as a subclass of this, it will be convenient to use:

require 'aws-cdk-lib'
require 'constructs'

class MyJob < AWSCDK::StepFunctions::StateMachineFragment
  attr_reader :start_state
  attr_reader :end_states

  def initialize(parent, id, props)
    super(parent, id)

    choice = AWSCDK::StepFunctions::Choice.new(self, "Choice")._when(AWSCDK::StepFunctions::Condition.string_equals("$.branch", "left"), AWSCDK::StepFunctions::Pass.new(self, "Left Branch"))._when(AWSCDK::StepFunctions::Condition.string_equals("$.branch", "right"), AWSCDK::StepFunctions::Pass.new(self, "Right Branch"))

    # ...

    @start_state = choice
    @end_states = choice.afterwards.end_states
  end
end

class MyStack < AWSCDK::Stack
  def initialize(scope, id)
    super(scope, id)
    # Do 3 different variants of MyJob in parallel
    parallel = AWSCDK::StepFunctions::Parallel.new(self, "All jobs").branch(MyJob.new(self, "Quick", {job_flavor: "quick"}).prefix_states).branch(MyJob.new(self, "Medium", {job_flavor: "medium"}).prefix_states).branch(MyJob.new(self, "Slow", {job_flavor: "slow"}).prefix_states)

    AWSCDK::StepFunctions::StateMachine.new(self, "MyStateMachine", {
        definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(parallel),
    })
  end
end

A few utility functions are available to parse state machine fragments.

Activity

Activities represent work that is done on some non-Lambda worker pool. The Step Functions workflow will submit work to this Activity, and a worker pool that you run yourself, probably on EC2, will pull jobs from the Activity and submit the results of individual jobs back.

You need the ARN to do so, so if you use Activities be sure to pass the Activity ARN into your worker pool:

activity = AWSCDK::StepFunctions::Activity.new(self, "Activity")

# Read this CloudFormation Output from your application and use it to poll for work on
# the activity.
AWSCDK::CfnOutput.new(self, "ActivityArn", {value: activity.activity_arn})

Activity-Level Permissions

Granting IAM permissions to an activity can be achieved by calling the grant(principal, actions) API:

activity = AWSCDK::StepFunctions::Activity.new(self, "Activity")

role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})

activity.grant(role, "states:SendTaskSuccess")

This will grant the IAM principal the specified actions onto the activity.

Metrics

Task object expose various metrics on the execution of that particular task. For example, to create an alarm on a particular task failing:

task = nil

AWSCDK::CloudWatch::Alarm.new(self, "TaskAlarm", {
    metric: task.metric_failed,
    threshold: 1,
    evaluation_periods: 1,
})

There are also metrics on the complete state machine:

state_machine = nil # AWSCDK::StepFunctions::StateMachine

AWSCDK::CloudWatch::Alarm.new(self, "StateMachineAlarm", {
    metric: state_machine.metric_failed,
    threshold: 1,
    evaluation_periods: 1,
})

And there are metrics on the capacity of all state machines in your account:

AWSCDK::CloudWatch::Alarm.new(self, "ThrottledAlarm", {
    metric: AWSCDK::StepFunctions::StateTransitionMetric.metric_throttled_events,
    threshold: 10,
    evaluation_periods: 2,
})

Error names

Step Functions identifies errors in the Amazon States Language using case-sensitive strings, known as error names. The Amazon States Language defines a set of built-in strings that name well-known errors, all beginning with the States. prefix.

Logging

Enable logging to CloudWatch by passing a logging configuration with a destination LogGroup:

require 'aws-cdk-lib'


log_group = AWSCDK::Logs::LogGroup.new(self, "MyLogGroup")

definition = AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "Pass"))

AWSCDK::StepFunctions::StateMachine.new(self, "MyStateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
    logs: {
        destination: log_group,
        level: AWSCDK::StepFunctions::LogLevel::ALL,
    },
})

Encryption

You can encrypt your data using a customer managed key for AWS Step Functions state machines and activities. You can configure a symmetric AWS KMS key and data key reuse period when creating or updating a State Machine or when creating an Activity. The execution history and state machine definition will be encrypted with the key applied to the State Machine. Activity inputs will be encrypted with the key applied to the Activity.

Encrypting state machines

You can provide a symmetric KMS key to encrypt the state machine definition and execution history:

require 'aws-cdk-lib'


kms_key = AWSCDK::KMS::Key.new(self, "Key")
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachineWithCMKEncryptionConfiguration", {
    state_machine_name: "StateMachineWithCMKEncryptionConfiguration",
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "Pass"))),
    state_machine_type: AWSCDK::StepFunctions::StateMachineType::STANDARD,
    encryption_configuration: AWSCDK::StepFunctions::CustomerManagedEncryptionConfiguration.new(kms_key, AWSCDK::Duration.seconds(60)),
})

Encrypting state machine logs in Cloud Watch Logs

If a state machine is encrypted with a customer managed key and has logging enabled, its decrypted execution history will be stored in CloudWatch Logs. If you want to encrypt the logs from the state machine using your own KMS key, you can do so by configuring the LogGroup associated with the state machine to use a KMS key.

require 'aws-cdk-lib'


state_machine_kms_key = AWSCDK::KMS::Key.new(self, "StateMachine Key")
log_group_key = AWSCDK::KMS::Key.new(self, "LogGroup Key")

#
#   Required KMS key policy which allows the CloudWatchLogs service principal to encrypt the entire log group using the
#   customer managed kms key. See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html#cmk-permissions
#
log_group_key.add_to_resource_policy(AWSCDK::IAM::PolicyStatement.new({
    resources: ["*"],
    actions: ["kms:Encrypt*", "kms:Decrypt*", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:Describe*"],
    principals: [AWSCDK::IAM::ServicePrincipal.new("logs.#{AWSCDK::Stack.of(self).region}.amazonaws.com")],
    conditions: {
        ArnEquals: {
            "kms:EncryptionContext:aws:logs:arn" => AWSCDK::Stack.of(self).format_arn({
                service: "logs",
                resource: "log-group",
                sep: ":",
                resource_name: "/aws/vendedlogs/states/MyLogGroup",
            }),
        },
    },
}))

# Create logGroup and provding encryptionKey which will be used to encrypt the log group
log_group = AWSCDK::Logs::LogGroup.new(self, "MyLogGroup", {
    log_group_name: "/aws/vendedlogs/states/MyLogGroup",
    encryption_key: log_group_key,
})

# Create state machine with CustomerManagedEncryptionConfiguration
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachineWithCMKWithCWLEncryption", {
    state_machine_name: "StateMachineWithCMKWithCWLEncryption",
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "PassState", {
        result: AWSCDK::StepFunctions::Result.from_string("Hello World"),
    }))),
    state_machine_type: AWSCDK::StepFunctions::StateMachineType::STANDARD,
    encryption_configuration: AWSCDK::StepFunctions::CustomerManagedEncryptionConfiguration.new(state_machine_kms_key),
    logs: {
        destination: log_group,
        level: AWSCDK::StepFunctions::LogLevel::ALL,
        include_execution_data: true,
    },
})

Encrypting activity inputs

When you provide a symmetric KMS key, all inputs from the Step Functions Activity will be encrypted using the provided KMS key:

require 'aws-cdk-lib'


kms_key = AWSCDK::KMS::Key.new(self, "Key")
activity = AWSCDK::StepFunctions::Activity.new(self, "ActivityWithCMKEncryptionConfiguration", {
    activity_name: "ActivityWithCMKEncryptionConfiguration",
    encryption_configuration: AWSCDK::StepFunctions::CustomerManagedEncryptionConfiguration.new(kms_key, AWSCDK::Duration.seconds(75)),
})

Changing Encryption

If you want to switch encryption from a customer provided key to a Step Functions owned key or vice-versa you must explicitly provide encryptionConfiguration?

Example: Switching from a customer managed key to a Step Functions owned key for StateMachine

Before

require 'aws-cdk-lib'


kms_key = AWSCDK::KMS::Key.new(self, "Key")
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    state_machine_name: "StateMachine",
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "Pass"))),
    state_machine_type: AWSCDK::StepFunctions::StateMachineType::STANDARD,
    encryption_configuration: AWSCDK::StepFunctions::CustomerManagedEncryptionConfiguration.new(kms_key, AWSCDK::Duration.seconds(60)),
})

After

state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    state_machine_name: "StateMachine",
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "Pass"))),
    state_machine_type: AWSCDK::StepFunctions::StateMachineType::STANDARD,
    encryption_configuration: AWSCDK::StepFunctions::AWSOwnedEncryptionConfiguration.new,
})

X-Ray tracing

Enable X-Ray tracing for StateMachine:

definition = AWSCDK::StepFunctions::Chain.start(AWSCDK::StepFunctions::Pass.new(self, "Pass"))

AWSCDK::StepFunctions::StateMachine.new(self, "MyStateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
    tracing_enabled: true,
})

See the AWS documentation to learn more about AWS Step Functions's X-Ray support.

State Machine Permission Grants

IAM roles, users, or groups which need to be able to work with a State Machine should be granted IAM permissions.

Any object that implements the IGrantable interface (has an associated principal) can be granted permissions by calling:

Start Execution Permission

Grant permission to start an execution of a state machine by calling the grant_start_execution() API.

definition = nil # AWSCDK::StepFunctions::IChainable
role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# Give role permission to start execution of state machine
state_machine.grant_start_execution(role)

The following permission is provided to a service principal by the grant_start_execution() API:

Read Permissions

Grant read access to a state machine by calling the grant_read() API.

definition = nil # AWSCDK::StepFunctions::IChainable
role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# Give role read access to state machine
state_machine.grant_read(role)

The following read permissions are provided to a service principal by the grant_read() API:

Task Response Permissions

Grant permission to allow task responses to a state machine by calling the grant_task_response() API:

definition = nil # AWSCDK::StepFunctions::IChainable
role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# Give role task response permissions to the state machine
state_machine.grant_task_response(role)

The following read permissions are provided to a service principal by the grant_read() API:

Execution-level Permissions

Grant execution-level permissions to a state machine by calling the grant_execution() API:

Redrive Execution Permission

Grant the given identity permission to redrive the execution of the state machine:

definition = nil # AWSCDK::StepFunctions::IChainable
role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# Give role permission to start execution of state machine
state_machine.grant_start_execution(role)
# Give role permission to redrive any executions of the state machine
state_machine.grant_redrive_execution(role)
definition = nil # AWSCDK::StepFunctions::IChainable
role = AWSCDK::IAM::Role.new(self, "Role", {
    assumed_by: AWSCDK::IAM::ServicePrincipal.new("lambda.amazonaws.com"),
})
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# Give role permission to get execution history of ALL executions for the state machine
state_machine.grant_execution(role, "states:GetExecutionHistory")

Custom Permissions

You can add any set of permissions to a state machine by calling the grant() API.

definition = nil # AWSCDK::StepFunctions::IChainable
user = AWSCDK::IAM::User.new(self, "MyUser")
state_machine = AWSCDK::StepFunctions::StateMachine.new(self, "StateMachine", {
    definition_body: AWSCDK::StepFunctions::DefinitionBody.from_chainable(definition),
})

# give user permission to send task success to the state machine
state_machine.grant(user, "states:SendTaskSuccess")

Import

Any Step Functions state machine that has been created outside the stack can be imported into your CDK stack.

State machines can be imported by their ARN via the StateMachine.fromStateMachineArn() API. In addition, the StateMachine can be imported via the StateMachine.fromStateMachineName() method, as long as they are in the same account/region as the current construct.

app = AWSCDK::App.new
stack = AWSCDK::Stack.new(app, "MyStack")
AWSCDK::StepFunctions::StateMachine.from_state_machine_arn(self, "ViaArnImportedStateMachine", "arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ")

AWSCDK::StepFunctions::StateMachine.from_state_machine_name(self, "ViaResourceNameImportedStateMachine", "StateMachine2E01A3A5-N5TJppzoevKQ")

API Reference

Classes 50

ActivityDefine a new Step Functions Activity. AWSOwnedEncryptionConfigurationDefine a new AwsOwnedEncryptionConfiguration. CfnActivityAn activity is a task that you write in any programming language and host on any machine t CfnStateMachineProvisions a state machine. CfnStateMachineAliasRepresents a state machine [alias](https://docs.aws.amazon.com/step-functions/latest/dg/co CfnStateMachineVersionRepresents a state machine [version](https://docs.aws.amazon.com/step-functions/latest/dg/ ChainA collection of states to chain onto. ChainDefinitionBody ChoiceDefine a Choice in the state machine. ConditionA Condition for use in a Choice state branch. CsvHeadersConfiguration for CSV header options for a CSV Item Reader. CustomerManagedEncryptionConfigurationDefine a new CustomerManagedEncryptionConfiguration. CustomStateState defined by supplying Amazon States Language (ASL) in the state machine. DefinitionBody DistributedMapDefine a Distributed Mode Map state in the state machine. EncryptionConfigurationBase class for creating an EncryptionConfiguration for either state machines or activities ErrorsPredefined error strings Error names in Amazon States Language - https://states-language.n FailDefine a Fail state in the state machine. FieldUtilsHelper functions to work with structures containing fields. FileDefinitionBody ItemBatcherConfiguration for processing a group of items in a single child workflow execution. JsonPathExtract a field from the State Machine data or context that gets passed around between sta MapDefine a Map state in the state machine. MapBaseDefine a Map state in the state machine. ParallelDefine a Parallel state in the state machine. PassDefine a Pass in the state machine. ProvideItemsThe array that the Map state will iterate over. ResultThe result of a Pass operation. ResultWriterConfiguration for writing Distributed Map state results to S3. ResultWriterV2Configuration for writing Distributed Map state results to S3 The ResultWriter field canno S3CsvItemReaderItem Reader configuration for iterating over items in a CSV file stored in S3. S3JsonItemReaderItem Reader configuration for iterating over items in a JSON array stored in a S3 file. S3JsonLItemReaderItem Reader configuration for iterating over the rows of the JSONL file stored in S3. S3ManifestItemReaderItem Reader configuration for iterating over items in a S3 inventory manifest file stored S3ObjectsItemReaderItem Reader configuration for iterating over objects in an S3 bucket. StateBase class for all other state classes. StateGraphA collection of connected states. StateMachineDefine a StepFunctions State Machine. StateMachineFragmentBase class for reusable state machine fragments. StateMachineGrantsCollection of grant methods for a IStateMachineRef. StateTransitionMetricMetrics on the rate limiting performed on state machine execution. StringDefinitionBody SucceedDefine a Succeed state in the state machine. TaskInputType union for task classes that accept multiple types of payload. TaskRoleRole to be assumed by the State Machine's execution role for invoking a task's resource. TaskStateBaseDefine a Task state in the state machine. TimeoutTimeout for a task or heartbeat. WaitDefine a Wait state in the state machine. WaitTimeRepresents the Wait state which delays a state machine from continuing for a specified tim WriterConfigConfiguration to format the output.

Interfaces 72

ActivityPropsProperties for defining a new Step Functions Activity. AfterwardsOptionsOptions for selecting the choice paths. AssignableStateOptionsOption properties for state that can assign variables. CatchPropsError handler details. CfnActivityPropsProperties for defining a `CfnActivity`. CfnStateMachineAliasPropsProperties for defining a `CfnStateMachineAlias`. CfnStateMachinePropsProperties for defining a `CfnStateMachine`. CfnStateMachineVersionPropsProperties for defining a `CfnStateMachineVersion`. ChoiceJsonataPropsProperties for defining a Choice state that using JSONata. ChoiceJsonPathPropsProperties for defining a Choice state that using JSONPath. ChoicePropsProperties for defining a Choice state. ChoiceTransitionOptionsOptions for Choice Transition. CredentialsSpecifies a target role assumed by the State Machine's execution role for invoking the tas CustomStatePropsProperties for defining a custom state definition. DefinitionConfigPartial object from the StateMachine L1 construct properties containing definition informa DistributedMapJsonataPropsProperties for configuring a Distribute Map state that using JSONata. DistributedMapJsonPathPropsProperties for configuring a Distribute Map state that using JSONPath. DistributedMapPropsProperties for configuring a Distribute Map state. FailJsonataPropsProperties for defining a Fail state that using JSONata. FailJsonPathPropsProperties for defining a Fail state that using JSONPath. FailPropsProperties for defining a Fail state. FindStateOptionsOptions for finding reachable states. IActivityRepresents a Step Functions Activity https://docs.aws.amazon.com/step-functions/latest/dg/ IChainableInterface for objects that can be used in a Chain. IItemReaderBase interface for Item Reader configurations. INextableInterface for states that can have 'next' states. IStateMachineA State Machine. ItemBatcherPropsInterface for ItemBatcher configuration properties. ItemReaderPropsBase interface for Item Reader configuration properties. JsonataCommonOptionsOption properties for JSONata state. JsonataStateOptionsOption properties for JSONata task state. JsonataStatePropsProperties shared by all states that use JSONata. JsonPathCommonOptionsOption properties for JSONPath state. JsonPathStatePropsProperties shared by all states that use JSONPath. LogOptionsDefines what execution history events are logged and where they are logged. MapBaseJsonataOptionsBase properties for defining a Map state that using JSONata. MapBaseJsonPathOptionsBase properties for defining a Map state that using JSONPath. MapBaseOptionsBase properties for defining a Map state. MapBasePropsProperties for defining a Map state. MapJsonataPropsProperties for defining a Map state that using JSONata. MapJsonPathPropsProperties for defining a Map state that using JSONPath. MapPropsProperties for defining a Map state. ParallelJsonataPropsProperties for defining a Parallel state that using JSONata. ParallelJsonPathPropsProperties for defining a Parallel state that using JSONPath. ParallelPropsProperties for defining a Parallel state. PassJsonataPropsProperties for defining a Pass state that using JSONata. PassJsonPathPropsProperties for defining a Pass state that using JSONPath. PassPropsProperties for defining a Pass state. ProcessorConfigSpecifies the configuration for the processor Map state. ResultWriterPropsInterface for Result Writer configuration props. ResultWriterV2PropsInterface for Result Writer configuration props. RetryPropsRetry details. S3CsvItemReaderPropsProperties for configuring an Item Reader that iterates over items in a CSV file in S3. S3FileItemReaderPropsBase interface for Item Reader configuration properties the iterate over entries in a S3 f S3ObjectsItemReaderPropsProperties for configuring an Item Reader that iterates over objects in an S3 bucket. SingleStateOptionsOptions for creating a single state. StateBasePropsProperties shared by all states. StateMachineGrantsPropsProperties for StateMachineGrants. StateMachinePropsProperties for defining a State Machine. StatePropsProperties shared by all states. SucceedJsonataPropsProperties for defining a Succeed state that using JSONata. SucceedJsonPathPropsProperties for defining a Succeed state that using JSONPath. SucceedPropsProperties for defining a Succeed state. TaskMetricsConfigTask Metrics. TaskStateBaseOptionsBase options for all task states. TaskStateBasePropsProps that are common to all tasks. TaskStateJsonataBasePropsProps that are common to all tasks that using JSONata. TaskStateJsonPathBasePropsProps that are common to all tasks that using JSONPath. WaitJsonataPropsProperties for defining a Wait state that using JSONata. WaitJsonPathPropsProperties for defining a Wait state that using JSONPath. WaitPropsProperties for defining a Wait state. WriterConfigPropsInterface for Writer Config props.

Enums 13

CsvDelimiterDelimiter used in CSV file. CsvHeaderLocationCSV header location options. InputTypeThe type of task input. IntegrationPatternAWS Step Functions integrates with services directly in the Amazon States Language. JitterTypeValues allowed in the retrier JitterStrategy field. LogLevelDefines which category of execution history events are logged. OutputTypeThe format of the Output of the child workflow executions. ProcessorModeMode of the Map workflow. ProcessorTypeExecution type for the Map workflow. QueryLanguageThe name of the query language used by the state machine or state. ServiceIntegrationPatternThree ways to call an integrated service: Request Response, Run a Job and Wait for a Callb StateMachineTypeTwo types of state machines are available in AWS Step Functions: EXPRESS AND STANDARD. TransformationThe transformation to be applied to the Output of the Child Workflow executions.