Skip to content

Commit e1d5ff1

Browse files
shivlakskarupanerura
authored andcommitted
feat(stepfunctions): custom state as an escape hatch
Custom State which enables the capability to provide Amazon States Language (ASL) JSON as an escape hatch. Useful when there are capabilities that are offered through Step Functions such as service integrations, and state properties but there isn't support through the CDK yet. It enables the usage of all service integrations we don't currently support.
1 parent c0ae50c commit e1d5ff1

File tree

6 files changed

+258
-0
lines changed

6 files changed

+258
-0
lines changed

packages/@aws-cdk/aws-stepfunctions/README.md

+71
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ are supported:
113113
* [`Succeed`](#succeed)
114114
* [`Fail`](#fail)
115115
* [`Map`](#map)
116+
* [`Custom State`](#custom-state)
116117

117118
An arbitrary JSON object (specified at execution start) is passed from state to
118119
state and transformed during the execution of the workflow. For more
@@ -256,6 +257,76 @@ const map = new stepfunctions.Map(this, 'Map State', {
256257
map.iterator(new stepfunctions.Pass(this, 'Pass State'));
257258
```
258259

260+
### Custom State
261+
262+
It's possible that the high-level constructs for the states or `stepfunctions-tasks` do not have
263+
the states or service integrations you are looking for. The primary reasons for this lack of
264+
functionality are:
265+
266+
* A [service integration](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-service-integrations.html) is available through Amazon States Langauge, but not available as construct
267+
classes in the CDK.
268+
* The state or state properties are available through Step Functions, but are not configurable
269+
through constructs
270+
271+
If a feature is not available, a `CustomState` can be used to supply any Amazon States Language
272+
JSON-based object as the state definition.
273+
274+
[Code Snippets](https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-code-snippet.html#tutorial-code-snippet-1) are available and can be plugged in as the state definition.
275+
276+
Custom states can be chained together with any of the other states to create your state machine
277+
definition. You will also need to provide any permissions that are required to the `role` that
278+
the State Machine uses.
279+
280+
The following example uses the `DynamoDB` service integration to insert data into a DynamoDB table.
281+
282+
```ts
283+
import * as ddb from '@aws-cdk/aws-dynamodb';
284+
import * as cdk from '@aws-cdk/core';
285+
import * as sfn from '@aws-cdk/aws-stepfunctions';
286+
287+
// create a table
288+
const table = new ddb.Table(this, 'montable', {
289+
partitionKey: {
290+
name: 'id',
291+
type: ddb.AttributeType.STRING,
292+
},
293+
});
294+
295+
const finalStatus = new sfn.Pass(stack, 'final step');
296+
297+
// States language JSON to put an item into DynamoDB
298+
// snippet generated from https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-code-snippet.html#tutorial-code-snippet-1
299+
const stateJson = {
300+
Type: 'Task',
301+
Resource: 'arn:aws:states:::dynamodb:putItem',
302+
Parameters: {
303+
TableName: table.tableName,
304+
Item: {
305+
id: {
306+
S: 'MyEntry',
307+
},
308+
},
309+
},
310+
ResultPath: null,
311+
};
312+
313+
// custom state which represents a task to insert data into DynamoDB
314+
const custom = new sfn.CustomState(this, 'my custom task', {
315+
stateJson,
316+
});
317+
318+
const chain = sfn.Chain.start(custom)
319+
.next(finalStatus);
320+
321+
const sm = new sfn.StateMachine(this, 'StateMachine', {
322+
definition: chain,
323+
timeout: cdk.Duration.seconds(30),
324+
});
325+
326+
// don't forget permissions. You need to assign them
327+
table.grantWriteData(sm.role);
328+
```
329+
259330
## Task Chaining
260331

261332
To make defining work flows as convenient (and readable in a top-to-bottom way)

packages/@aws-cdk/aws-stepfunctions/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export * from './states/succeed';
1919
export * from './states/task';
2020
export * from './states/wait';
2121
export * from './states/map';
22+
export * from './states/custom-state';
2223

2324
// AWS::StepFunctions CloudFormation Resources:
2425
export * from './stepfunctions.generated';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import { Chain } from '..';
3+
import { IChainable, INextable } from '../types';
4+
import { State } from './state';
5+
6+
/**
7+
* Properties for defining a custom state definition
8+
*/
9+
export interface CustomStateProps {
10+
/**
11+
* Amazon States Language (JSON-based) definition of the state
12+
*
13+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html
14+
*/
15+
readonly stateJson: { [key: string]: any };
16+
}
17+
18+
/**
19+
* State defined by supplying Amazon States Language (ASL) in the state machine.
20+
*
21+
* @experimental
22+
*/
23+
export class CustomState extends State implements IChainable, INextable {
24+
public readonly endStates: INextable[];
25+
26+
/**
27+
* Amazon States Language (JSON-based) definition of the state
28+
*/
29+
private readonly stateJson: { [key: string]: any};
30+
31+
constructor(scope: cdk.Construct, id: string, props: CustomStateProps) {
32+
super(scope, id, {});
33+
34+
this.endStates = [this];
35+
this.stateJson = props.stateJson;
36+
}
37+
38+
/**
39+
* Continue normal execution with the given state
40+
*/
41+
public next(next: IChainable): Chain {
42+
super.makeNext(next.startState);
43+
return Chain.sequence(this, next);
44+
}
45+
46+
/**
47+
* Returns the Amazon States Language object for this state
48+
*/
49+
public toStateJson(): object {
50+
return {
51+
...this.renderNextEnd(),
52+
...this.stateJson,
53+
};
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import '@aws-cdk/assert/jest';
2+
import * as cdk from '@aws-cdk/core';
3+
import * as stepfunctions from '../lib';
4+
5+
describe('Custom State', () => {
6+
test('maintains the state Json provided during construction', () => {
7+
// GIVEN
8+
const stack = new cdk.Stack();
9+
const stateJson = {
10+
Type: 'Task',
11+
Resource: 'arn:aws:states:::dynamodb:putItem',
12+
Parameters: {
13+
TableName: 'MyTable',
14+
Item: {
15+
id: {
16+
S: 'MyEntry',
17+
},
18+
},
19+
},
20+
ResultPath: null,
21+
};
22+
23+
// WHEN
24+
const customState = new stepfunctions.CustomState(stack, 'Custom', {
25+
stateJson,
26+
});
27+
28+
// THEN
29+
expect(customState.toStateJson()).toStrictEqual({
30+
...stateJson,
31+
End: true,
32+
});
33+
});
34+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"Resources": {
3+
"StateMachineRoleB840431D": {
4+
"Type": "AWS::IAM::Role",
5+
"Properties": {
6+
"AssumeRolePolicyDocument": {
7+
"Statement": [
8+
{
9+
"Action": "sts:AssumeRole",
10+
"Effect": "Allow",
11+
"Principal": {
12+
"Service": {
13+
"Fn::Join": [
14+
"",
15+
[
16+
"states.",
17+
{
18+
"Ref": "AWS::Region"
19+
},
20+
".amazonaws.com"
21+
]
22+
]
23+
}
24+
}
25+
}
26+
],
27+
"Version": "2012-10-17"
28+
}
29+
}
30+
},
31+
"StateMachine2E01A3A5": {
32+
"Type": "AWS::StepFunctions::StateMachine",
33+
"Properties": {
34+
"DefinitionString": "{\"StartAt\":\"my custom task\",\"States\":{\"my custom task\":{\"Next\":\"final step\",\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::dynamodb:putItem\",\"Parameters\":{\"TableName\":\"my-cool-table\",\"Item\":{\"id\":{\"S\":\"my-entry\"}}},\"ResultPath\":null},\"final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}",
35+
"RoleArn": {
36+
"Fn::GetAtt": [
37+
"StateMachineRoleB840431D",
38+
"Arn"
39+
]
40+
}
41+
},
42+
"DependsOn": [
43+
"StateMachineRoleB840431D"
44+
]
45+
}
46+
},
47+
"Outputs": {
48+
"StateMachineARN": {
49+
"Value": {
50+
"Ref": "StateMachine2E01A3A5"
51+
}
52+
}
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as sfn from '../lib';
3+
4+
/*
5+
* Stack verification steps:
6+
*
7+
* -- aws stepfunctions describe-state-machine --state-machine-arn <stack-output> has a status of `ACTIVE`
8+
*/
9+
const app = new cdk.App();
10+
const stack = new cdk.Stack(app, 'aws-stepfunctions-custom-state-integ');
11+
12+
const finalStatus = new sfn.Pass(stack, 'final step');
13+
14+
const stateJson = {
15+
Type: 'Task',
16+
Resource: 'arn:aws:states:::dynamodb:putItem',
17+
Parameters: {
18+
TableName: 'my-cool-table',
19+
Item: {
20+
id: {
21+
S: 'my-entry',
22+
},
23+
},
24+
},
25+
ResultPath: null,
26+
};
27+
28+
const custom = new sfn.CustomState(stack, 'my custom task', {
29+
stateJson,
30+
});
31+
32+
const chain = sfn.Chain.start(custom).next(finalStatus);
33+
34+
const sm = new sfn.StateMachine(stack, 'StateMachine', {
35+
definition: chain,
36+
timeout: cdk.Duration.seconds(30),
37+
});
38+
39+
new cdk.CfnOutput(stack, 'StateMachineARN', {
40+
value: sm.stateMachineArn,
41+
});
42+
43+
app.synth();

0 commit comments

Comments
 (0)