Skip to content

Commit 3000dd5

Browse files
authored
feat(stepfunctions-tasks): start a nested state machine execution as a construct (#8178)
This class is the replacement for the previous `StartExecution` class. There are a few differences: 1. the `stateMachine` parameter has been moved into props. Rationale: alignment with constructs. 2. the resource ARN that's generated in the Amazon States language uses `sync:2`. This returns an output of JSON instead of a string. Rationale: alignment with Step Functions team recommendation. 3. The `input` parameter has been changed to be of type `sfn.TaskInput` Rationale: previous type precluded the ability to assign state input to this parameter. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5c83652 commit 3000dd5

File tree

7 files changed

+586
-9
lines changed

7 files changed

+586
-9
lines changed

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

+8-9
Original file line numberDiff line numberDiff line change
@@ -728,15 +728,14 @@ const child = new sfn.StateMachine(stack, 'ChildStateMachine', {
728728
});
729729

730730
// Include the state machine in a Task state with callback pattern
731-
const task = new sfn.Task(stack, 'ChildTask', {
732-
task: new tasks.ExecuteStateMachine(child, {
733-
integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN,
734-
input: {
735-
token: sfn.Context.taskToken,
736-
foo: 'bar'
737-
},
738-
name: 'MyExecutionName'
739-
})
731+
const task = new StepFunctionsStartExecution(stack, 'ChildTask', {
732+
stateMachine: child,
733+
integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN,
734+
input: sfn.TaskInput.fromObject({
735+
token: sfn.Context.taskToken,
736+
foo: 'bar'
737+
}),
738+
name: 'MyExecutionName'
740739
});
741740

742741
// Define a second state machine with the Task state above

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

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from './sagemaker/sagemaker-task-base-types';
1414
export * from './sagemaker/sagemaker-train-task';
1515
export * from './sagemaker/sagemaker-transform-task';
1616
export * from './start-execution';
17+
export * from './stepfunctions/start-execution';
1718
export * from './evaluate-expression';
1819
export * from './emr/emr-create-cluster';
1920
export * from './emr/emr-set-cluster-termination-protection';

packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { getResourceArn } from './resource-arn-suffix';
55

66
/**
77
* Properties for StartExecution
8+
*
9+
* @deprecated - use 'StepFunctionsStartExecution'
810
*/
911
export interface StartExecutionProps {
1012
/**
@@ -39,6 +41,8 @@ export interface StartExecutionProps {
3941
* A Step Functions Task to call StartExecution on another state machine.
4042
*
4143
* It supports three service integration patterns: FIRE_AND_FORGET, SYNC and WAIT_FOR_TASK_TOKEN.
44+
*
45+
* @deprecated - use 'StepFunctionsStartExecution'
4246
*/
4347
export class StartExecution implements sfn.IStepFunctionsTask {
4448
private readonly integrationPattern: sfn.ServiceIntegrationPattern;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as sfn from '@aws-cdk/aws-stepfunctions';
3+
import { Construct, Stack } from '@aws-cdk/core';
4+
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
5+
6+
/**
7+
* Properties for StartExecution
8+
*/
9+
export interface StepFunctionsStartExecutionProps extends sfn.TaskStateBaseProps {
10+
/**
11+
* The Step Functions state machine to start the execution on.
12+
*/
13+
readonly stateMachine: sfn.IStateMachine;
14+
15+
/**
16+
* The JSON input for the execution, same as that of StartExecution.
17+
*
18+
* @see https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html
19+
*
20+
* @default - The state input (JSON path '$')
21+
*/
22+
readonly input?: sfn.TaskInput;
23+
24+
/**
25+
* The name of the execution, same as that of StartExecution.
26+
*
27+
* @see https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html
28+
*
29+
* @default - None
30+
*/
31+
readonly name?: string;
32+
}
33+
34+
/**
35+
* A Step Functions Task to call StartExecution on another state machine.
36+
*
37+
* It supports three service integration patterns: FIRE_AND_FORGET, SYNC and WAIT_FOR_TASK_TOKEN.
38+
*/
39+
export class StepFunctionsStartExecution extends sfn.TaskStateBase {
40+
private static readonly SUPPORTED_INTEGRATION_PATTERNS = [
41+
sfn.IntegrationPattern.REQUEST_RESPONSE,
42+
sfn.IntegrationPattern.RUN_JOB,
43+
sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN,
44+
];
45+
46+
protected readonly taskMetrics?: sfn.TaskMetricsConfig;
47+
protected readonly taskPolicies?: iam.PolicyStatement[];
48+
49+
private readonly integrationPattern: sfn.IntegrationPattern;
50+
51+
constructor(scope: Construct, id: string, private readonly props: StepFunctionsStartExecutionProps) {
52+
super(scope, id, props);
53+
54+
this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.REQUEST_RESPONSE;
55+
validatePatternSupported(this.integrationPattern, StepFunctionsStartExecution.SUPPORTED_INTEGRATION_PATTERNS);
56+
57+
if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.input)) {
58+
throw new Error('Task Token is required in `input` for callback. Use Context.taskToken to set the token.');
59+
}
60+
61+
this.taskPolicies = this.createScopedAccessPolicy();
62+
}
63+
64+
protected renderTask(): any {
65+
// suffix of ':2' indicates that the output of the nested state machine should be JSON
66+
// suffix is only applicable when waiting for a nested state machine to complete (RUN_JOB)
67+
// https://docs.aws.amazon.com/step-functions/latest/dg/connect-stepfunctions.html
68+
const suffix = this.integrationPattern === sfn.IntegrationPattern.RUN_JOB ? ':2' : '';
69+
return {
70+
Resource: `${integrationResourceArn('states', 'startExecution', this.integrationPattern)}${suffix}`,
71+
Parameters: sfn.FieldUtils.renderObject({
72+
Input: this.props.input ? this.props.input.value : sfn.TaskInput.fromDataAt('$').value,
73+
StateMachineArn: this.props.stateMachine.stateMachineArn,
74+
Name: this.props.name,
75+
}),
76+
};
77+
}
78+
79+
/**
80+
* As StateMachineArn is extracted automatically from the state machine object included in the constructor,
81+
*
82+
* the scoped access policy should be generated accordingly.
83+
*
84+
* This means the action of StartExecution should be restricted on the given state machine, instead of being granted to all the resources (*).
85+
*/
86+
private createScopedAccessPolicy(): iam.PolicyStatement[] {
87+
const stack = Stack.of(this);
88+
89+
const policyStatements = [
90+
new iam.PolicyStatement({
91+
actions: ['states:StartExecution'],
92+
resources: [this.props.stateMachine.stateMachineArn],
93+
}),
94+
];
95+
96+
// Step Functions use Cloud Watch managed rules to deal with synchronous tasks.
97+
if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) {
98+
policyStatements.push(
99+
new iam.PolicyStatement({
100+
actions: ['states:DescribeExecution', 'states:StopExecution'],
101+
// https://docs.aws.amazon.com/step-functions/latest/dg/concept-create-iam-advanced.html#concept-create-iam-advanced-execution
102+
resources: [
103+
stack.formatArn({
104+
service: 'states',
105+
resource: 'execution',
106+
sep: ':',
107+
resourceName: `${stack.parseArn(this.props.stateMachine.stateMachineArn, ':').resourceName}*`,
108+
}),
109+
],
110+
}),
111+
);
112+
113+
policyStatements.push(
114+
new iam.PolicyStatement({
115+
actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'],
116+
resources: [
117+
stack.formatArn({
118+
service: 'events',
119+
resource: 'rule',
120+
resourceName: 'StepFunctionsGetEventsForStepFunctionsExecutionRule',
121+
}),
122+
],
123+
}),
124+
);
125+
}
126+
127+
return policyStatements;
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
{
2+
"Resources": {
3+
"ChildRole1E3E0EF5": {
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+
"ChildDAB30558": {
32+
"Type": "AWS::StepFunctions::StateMachine",
33+
"Properties": {
34+
"DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}",
35+
"RoleArn": {
36+
"Fn::GetAtt": ["ChildRole1E3E0EF5", "Arn"]
37+
}
38+
},
39+
"DependsOn": ["ChildRole1E3E0EF5"]
40+
},
41+
"ParentRole5F0C366C": {
42+
"Type": "AWS::IAM::Role",
43+
"Properties": {
44+
"AssumeRolePolicyDocument": {
45+
"Statement": [
46+
{
47+
"Action": "sts:AssumeRole",
48+
"Effect": "Allow",
49+
"Principal": {
50+
"Service": {
51+
"Fn::Join": [
52+
"",
53+
[
54+
"states.",
55+
{
56+
"Ref": "AWS::Region"
57+
},
58+
".amazonaws.com"
59+
]
60+
]
61+
}
62+
}
63+
}
64+
],
65+
"Version": "2012-10-17"
66+
}
67+
}
68+
},
69+
"ParentRoleDefaultPolicy9BDC56DC": {
70+
"Type": "AWS::IAM::Policy",
71+
"Properties": {
72+
"PolicyDocument": {
73+
"Statement": [
74+
{
75+
"Action": "states:StartExecution",
76+
"Effect": "Allow",
77+
"Resource": {
78+
"Ref": "ChildDAB30558"
79+
}
80+
},
81+
{
82+
"Action": ["states:DescribeExecution", "states:StopExecution"],
83+
"Effect": "Allow",
84+
"Resource": {
85+
"Fn::Join": [
86+
"",
87+
[
88+
"arn:",
89+
{
90+
"Ref": "AWS::Partition"
91+
},
92+
":states:",
93+
{
94+
"Ref": "AWS::Region"
95+
},
96+
":",
97+
{
98+
"Ref": "AWS::AccountId"
99+
},
100+
":execution:",
101+
{
102+
"Fn::Select": [
103+
6,
104+
{
105+
"Fn::Split": [
106+
":",
107+
{
108+
"Ref": "ChildDAB30558"
109+
}
110+
]
111+
}
112+
]
113+
},
114+
"*"
115+
]
116+
]
117+
}
118+
},
119+
{
120+
"Action": ["events:PutTargets", "events:PutRule", "events:DescribeRule"],
121+
"Effect": "Allow",
122+
"Resource": {
123+
"Fn::Join": [
124+
"",
125+
[
126+
"arn:",
127+
{
128+
"Ref": "AWS::Partition"
129+
},
130+
":events:",
131+
{
132+
"Ref": "AWS::Region"
133+
},
134+
":",
135+
{
136+
"Ref": "AWS::AccountId"
137+
},
138+
":rule/StepFunctionsGetEventsForStepFunctionsExecutionRule"
139+
]
140+
]
141+
}
142+
}
143+
],
144+
"Version": "2012-10-17"
145+
},
146+
"PolicyName": "ParentRoleDefaultPolicy9BDC56DC",
147+
"Roles": [
148+
{
149+
"Ref": "ParentRole5F0C366C"
150+
}
151+
]
152+
}
153+
},
154+
"Parent8B210403": {
155+
"Type": "AWS::StepFunctions::StateMachine",
156+
"Properties": {
157+
"DefinitionString": {
158+
"Fn::Join": [
159+
"",
160+
[
161+
"{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:",
162+
{
163+
"Ref": "AWS::Partition"
164+
},
165+
":states:::states:startExecution.sync:2\",\"Parameters\":{\"Input\":{\"hello.$\":\"$.hello\"},\"StateMachineArn\":\"",
166+
{
167+
"Ref": "ChildDAB30558"
168+
},
169+
"\"}}}}"
170+
]
171+
]
172+
},
173+
"RoleArn": {
174+
"Fn::GetAtt": ["ParentRole5F0C366C", "Arn"]
175+
}
176+
},
177+
"DependsOn": ["ParentRoleDefaultPolicy9BDC56DC", "ParentRole5F0C366C"]
178+
}
179+
},
180+
"Outputs": {
181+
"StateMachineARN": {
182+
"Value": {
183+
"Ref": "Parent8B210403"
184+
}
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)