Skip to content

Commit 26e48c7

Browse files
authored
feat(stepfunctions): task and heartbeat timeout specified by a path (#23755)
Add support for dynamic timeouts referenced by a path in the state. Closes #15531 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 475dbef commit 26e48c7

17 files changed

+215
-36
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ new tasks.GlueStartJobRun(this, 'Task', {
907907
arguments: sfn.TaskInput.fromObject({
908908
key: 'value',
909909
}),
910-
timeout: Duration.minutes(30),
910+
taskTimeout: sfn.Timeout.duration(Duration.minutes(30)),
911911
notifyDelayAfter: Duration.minutes(5),
912912
});
913913
```

packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,14 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
191191
});
192192

193193
// validate timeout
194-
props.timeout !== undefined && withResolved(props.timeout.toSeconds(), (timeout) => {
195-
if (timeout < 60) {
196-
throw new Error(`attempt duration must be greater than 60 seconds. Received ${timeout} seconds.`);
197-
}
198-
});
194+
(props.timeout !== undefined || props.taskTimeout !== undefined) && withResolved(
195+
props.timeout?.toSeconds(),
196+
props.taskTimeout?.seconds, (timeout, taskTimeout) => {
197+
const definedTimeout = timeout ?? taskTimeout;
198+
if (definedTimeout && definedTimeout < 60) {
199+
throw new Error(`attempt duration must be greater than 60 seconds. Received ${definedTimeout} seconds.`);
200+
}
201+
});
199202

200203
// This is required since environment variables must not start with AWS_BATCH;
201204
// this naming convention is reserved for variables that are set by the AWS Batch service.
@@ -216,6 +219,15 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
216219
* @internal
217220
*/
218221
protected _renderTask(): any {
222+
let timeout: number | undefined = undefined;
223+
if (this.props.timeout) {
224+
timeout = this.props.timeout.toSeconds();
225+
} else if (this.props.taskTimeout?.seconds) {
226+
timeout = this.props.taskTimeout.seconds;
227+
} else if (this.props.taskTimeout?.path) {
228+
timeout = sfn.JsonPath.numberAt(this.props.taskTimeout.path);
229+
}
230+
219231
return {
220232
Resource: integrationResourceArn('batch', 'submitJob', this.integrationPattern),
221233
Parameters: sfn.FieldUtils.renderObject({
@@ -244,11 +256,12 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
244256
? { Attempts: this.props.attempts }
245257
: undefined,
246258

247-
Timeout: this.props.timeout
248-
? { AttemptDurationSeconds: this.props.timeout.toSeconds() }
259+
Timeout: timeout
260+
? { AttemptDurationSeconds: timeout }
249261
: undefined,
250262
}),
251263
TimeoutSeconds: undefined,
264+
TimeoutSecondsPath: undefined,
252265
};
253266
}
254267

packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,27 @@ export class GlueStartJobRun extends sfn.TaskStateBase {
8383
*/
8484
protected _renderTask(): any {
8585
const notificationProperty = this.props.notifyDelayAfter ? { NotifyDelayAfter: this.props.notifyDelayAfter.toMinutes() } : null;
86+
87+
let timeout: number | undefined = undefined;
88+
if (this.props.timeout) {
89+
timeout = this.props.timeout.toMinutes();
90+
} else if (this.props.taskTimeout?.seconds) {
91+
timeout = Duration.seconds(this.props.taskTimeout.seconds).toMinutes();
92+
} else if (this.props.taskTimeout?.path) {
93+
timeout = sfn.JsonPath.numberAt(this.props.taskTimeout.path);
94+
}
95+
8696
return {
8797
Resource: integrationResourceArn('glue', 'startJobRun', this.integrationPattern),
8898
Parameters: sfn.FieldUtils.renderObject({
8999
JobName: this.props.glueJobName,
90100
Arguments: this.props.arguments?.value,
91-
Timeout: this.props.timeout?.toMinutes(),
101+
Timeout: timeout,
92102
SecurityConfiguration: this.props.securityConfiguration,
93103
NotificationProperty: notificationProperty,
94104
}),
95105
TimeoutSeconds: undefined,
106+
TimeoutSecondsPath: undefined,
96107
};
97108
}
98109

packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class RunBatchStack extends cdk.Stack {
5353
foo: sfn.JsonPath.stringAt('$.bar'),
5454
}),
5555
attempts: 3,
56-
timeout: cdk.Duration.seconds(60),
56+
taskTimeout: sfn.Timeout.duration(cdk.Duration.seconds(60)),
5757
});
5858

5959
const definition = new sfn.Pass(this, 'Start', {

packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ test('Task with all the parameters', () => {
8686
foo: sfn.JsonPath.stringAt('$.bar'),
8787
}),
8888
attempts: 3,
89-
timeout: cdk.Duration.seconds(60),
89+
taskTimeout: sfn.Timeout.duration(cdk.Duration.seconds(60)),
9090
integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE,
9191
});
9292

@@ -132,7 +132,7 @@ test('supports tokens', () => {
132132
jobDefinitionArn: batchJobDefinition.jobDefinitionArn,
133133
jobQueueArn: batchJobQueue.jobQueueArn,
134134
arraySize: sfn.JsonPath.numberAt('$.arraySize'),
135-
timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')),
135+
taskTimeout: sfn.Timeout.at('$.timeout'),
136136
attempts: sfn.JsonPath.numberAt('$.attempts'),
137137
});
138138

@@ -328,7 +328,7 @@ test('Task throws if attempt duration is less than 60 sec', () => {
328328
jobDefinitionArn: batchJobDefinition.jobDefinitionArn,
329329
jobName: 'JobName',
330330
jobQueueArn: batchJobQueue.jobQueueArn,
331-
timeout: cdk.Duration.seconds(59),
331+
taskTimeout: sfn.Timeout.duration(cdk.Duration.seconds(59)),
332332
});
333333
}).toThrow(
334334
/attempt duration must be greater than 60 seconds./,

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.js.snapshot/aws-sfn-tasks-ecs-fargate-integ.assets.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "21.0.0",
2+
"version": "29.0.0",
33
"files": {
4-
"998dbb594bb7c679342b230e008a9707c46a79167240b84e7fd60529137d2fe5": {
4+
"0ee63f9e3418b742ec39af14137ee6aff6c03be8cbee3f04c27b663df1cbabd6": {
55
"source": {
66
"path": "aws-sfn-tasks-ecs-fargate-integ.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "998dbb594bb7c679342b230e008a9707c46a79167240b84e7fd60529137d2fe5.json",
12+
"objectKey": "0ee63f9e3418b742ec39af14137ee6aff6c03be8cbee3f04c27b663df1cbabd6.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.js.snapshot/aws-sfn-tasks-ecs-fargate-integ.template.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@
775775
"Fn::Join": [
776776
"",
777777
[
778-
"{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"FargateTask\"},\"FargateTask\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:",
778+
"{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\",\"Timeout\":900},\"Next\":\"FargateTask\"},\"FargateTask\":{\"End\":true,\"Type\":\"Task\",\"TimeoutSecondsPath\":\"$.Timeout\",\"Resource\":\"arn:",
779779
{
780780
"Ref": "AWS::Partition"
781781
},
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"21.0.0"}
1+
{"version":"29.0.0"}

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.js.snapshot/integ.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "21.0.0",
2+
"version": "29.0.0",
33
"testCases": {
44
"integ.fargate-run-task": {
55
"stacks": [

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.js.snapshot/manifest.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "21.0.0",
2+
"version": "29.0.0",
33
"artifacts": {
44
"aws-sfn-tasks-ecs-fargate-integ.assets": {
55
"type": "cdk:asset-manifest",
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/998dbb594bb7c679342b230e008a9707c46a79167240b84e7fd60529137d2fe5.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0ee63f9e3418b742ec39af14137ee6aff6c03be8cbee3f04c27b663df1cbabd6.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.js.snapshot/tree.json

+26-2
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,14 @@
681681
"id": "TaskRole",
682682
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/TaskRole",
683683
"children": {
684+
"ImportTaskRole": {
685+
"id": "ImportTaskRole",
686+
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/TaskRole/ImportTaskRole",
687+
"constructInfo": {
688+
"fqn": "@aws-cdk/core.Resource",
689+
"version": "0.0.0"
690+
}
691+
},
684692
"Resource": {
685693
"id": "Resource",
686694
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/TaskRole/Resource",
@@ -828,6 +836,14 @@
828836
"id": "ExecutionRole",
829837
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/ExecutionRole",
830838
"children": {
839+
"ImportExecutionRole": {
840+
"id": "ImportExecutionRole",
841+
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/ExecutionRole/ImportExecutionRole",
842+
"constructInfo": {
843+
"fqn": "@aws-cdk/core.Resource",
844+
"version": "0.0.0"
845+
}
846+
},
831847
"Resource": {
832848
"id": "Resource",
833849
"path": "aws-sfn-tasks-ecs-fargate-integ/TaskDef/ExecutionRole/Resource",
@@ -1008,6 +1024,14 @@
10081024
"id": "Role",
10091025
"path": "aws-sfn-tasks-ecs-fargate-integ/StateMachine/Role",
10101026
"children": {
1027+
"ImportRole": {
1028+
"id": "ImportRole",
1029+
"path": "aws-sfn-tasks-ecs-fargate-integ/StateMachine/Role/ImportRole",
1030+
"constructInfo": {
1031+
"fqn": "@aws-cdk/core.Resource",
1032+
"version": "0.0.0"
1033+
}
1034+
},
10111035
"Resource": {
10121036
"id": "Resource",
10131037
"path": "aws-sfn-tasks-ecs-fargate-integ/StateMachine/Role/Resource",
@@ -1258,7 +1282,7 @@
12581282
"Fn::Join": [
12591283
"",
12601284
[
1261-
"{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"FargateTask\"},\"FargateTask\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:",
1285+
"{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\",\"Timeout\":900},\"Next\":\"FargateTask\"},\"FargateTask\":{\"End\":true,\"Type\":\"Task\",\"TimeoutSecondsPath\":\"$.Timeout\",\"Resource\":\"arn:",
12621286
{
12631287
"Ref": "AWS::Partition"
12641288
},
@@ -1336,7 +1360,7 @@
13361360
"path": "Tree",
13371361
"constructInfo": {
13381362
"fqn": "constructs.Construct",
1339-
"version": "10.1.140"
1363+
"version": "10.1.216"
13401364
}
13411365
}
13421366
},

packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-run-task.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const containerDefinition = taskDefinition.addContainer('TheContainer', {
3232

3333
// Build state machine
3434
const definition = new sfn.Pass(stack, 'Start', {
35-
result: sfn.Result.fromObject({ SomeKey: 'SomeValue' }),
35+
result: sfn.Result.fromObject({ SomeKey: 'SomeValue', Timeout: 900 }),
3636
}).next(
3737
new tasks.EcsRunTask(stack, 'FargateTask', {
3838
integrationPattern: sfn.IntegrationPattern.RUN_JOB,
@@ -53,6 +53,7 @@ const definition = new sfn.Pass(stack, 'Start', {
5353
launchTarget: new tasks.EcsFargateLaunchTarget({
5454
platformVersion: ecs.FargatePlatformVersion.VERSION1_4,
5555
}),
56+
taskTimeout: sfn.Timeout.at('$.Timeout'),
5657
}),
5758
);
5859

packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,4 @@ describeDeprecated('RunGlueJobTask', () => {
141141
});
142142
}).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i);
143143
});
144-
});
144+
});

packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test('Invoke glue job with full properties', () => {
5252
glueJobName,
5353
integrationPattern: sfn.IntegrationPattern.RUN_JOB,
5454
arguments: sfn.TaskInput.fromObject(jobArguments),
55-
timeout: glueJobTimeout,
55+
taskTimeout: sfn.Timeout.duration(glueJobTimeout),
5656
securityConfiguration,
5757
notifyDelayAfter,
5858
});
@@ -87,6 +87,37 @@ test('Invoke glue job with full properties', () => {
8787
});
8888
});
8989

90+
test('Invoke glue job with Timeout.at()', () => {
91+
const task = new GlueStartJobRun(stack, 'Task', {
92+
glueJobName,
93+
taskTimeout: sfn.Timeout.at('$.timeout'),
94+
});
95+
new sfn.StateMachine(stack, 'SM', {
96+
definition: task,
97+
});
98+
99+
expect(stack.resolve(task.toStateJson())).toEqual({
100+
Type: 'Task',
101+
Resource: {
102+
'Fn::Join': [
103+
'',
104+
[
105+
'arn:',
106+
{
107+
Ref: 'AWS::Partition',
108+
},
109+
':states:::glue:startJobRun',
110+
],
111+
],
112+
},
113+
End: true,
114+
Parameters: {
115+
'JobName': glueJobName,
116+
'Timeout.$': '$.timeout',
117+
},
118+
});
119+
});
120+
90121
test('job arguments can reference state input', () => {
91122
const task = new GlueStartJobRun(stack, 'Task', {
92123
glueJobName,

0 commit comments

Comments
 (0)