1 | # AWS Step Functions Construct Library
|
2 |
|
3 |
|
4 | ---
|
5 |
|
6 | ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge)
|
7 |
|
8 | ![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge)
|
9 |
|
10 | ---
|
11 |
|
12 |
|
13 |
|
14 | The `@aws-cdk/aws-stepfunctions` package contains constructs for building
|
15 | serverless workflows using objects. Use this in conjunction with the
|
16 | `@aws-cdk/aws-stepfunctions-tasks` package, which contains classes used
|
17 | to call other AWS services.
|
18 |
|
19 | Defining a workflow looks like this (for the [Step Functions Job Poller
|
20 | example](https://docs.aws.amazon.com/step-functions/latest/dg/job-status-poller-sample.html)):
|
21 |
|
22 | ## Example
|
23 |
|
24 | ```ts
|
25 | import * as sfn from '@aws-cdk/aws-stepfunctions';
|
26 | import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';
|
27 | import * as lambda from '@aws-cdk/aws-lambda';
|
28 |
|
29 | const submitLambda = new lambda.Function(this, 'SubmitLambda', { ... });
|
30 | const getStatusLambda = new lambda.Function(this, 'CheckLambda', { ... });
|
31 |
|
32 | const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', {
|
33 | lambdaFunction: submitLambda,
|
34 | // Lambda's result is in the attribute `Payload`
|
35 | outputPath: '$.Payload',
|
36 | });
|
37 |
|
38 | const waitX = new sfn.Wait(this, 'Wait X Seconds', {
|
39 | time: sfn.WaitTime.secondsPath('$.waitSeconds'),
|
40 | });
|
41 |
|
42 | const getStatus = new tasks.LambdaInvoke(this, 'Get Job Status', {
|
43 | lambdaFunction: getStatusLambda,
|
44 | // Pass just the field named "guid" into the Lambda, put the
|
45 | // Lambda's result in a field called "status" in the response
|
46 | inputPath: '$.guid',
|
47 | outputPath: '$.Payload',
|
48 | });
|
49 |
|
50 | const jobFailed = new sfn.Fail(this, 'Job Failed', {
|
51 | cause: 'AWS Batch Job Failed',
|
52 | error: 'DescribeJob returned FAILED',
|
53 | });
|
54 |
|
55 | const finalStatus = new tasks.LambdaInvoke(this, 'Get Final Job Status', {
|
56 | lambdaFunction: getStatusLambda,
|
57 | // Use "guid" field as input
|
58 | inputPath: '$.guid',
|
59 | outputPath: '$.Payload',
|
60 | });
|
61 |
|
62 | const definition = submitJob
|
63 | .next(waitX)
|
64 | .next(getStatus)
|
65 | .next(new sfn.Choice(this, 'Job Complete?')
|
66 | // Look at the "status" field
|
67 | .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
|
68 | .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus)
|
69 | .otherwise(waitX));
|
70 |
|
71 | new sfn.StateMachine(this, 'StateMachine', {
|
72 | definition,
|
73 | timeout: Duration.minutes(5)
|
74 | });
|
75 | ```
|
76 |
|
77 | You can find more sample snippets and learn more about the service integrations
|
78 | in the `@aws-cdk/aws-stepfunctions-tasks` package.
|
79 |
|
80 | ## State Machine
|
81 |
|
82 | A `stepfunctions.StateMachine` is a resource that takes a state machine
|
83 | definition. The definition is specified by its start state, and encompasses
|
84 | all states reachable from the start state:
|
85 |
|
86 | ```ts
|
87 | const startState = new sfn.Pass(this, 'StartState');
|
88 |
|
89 | new sfn.StateMachine(this, 'StateMachine', {
|
90 | definition: startState
|
91 | });
|
92 | ```
|
93 |
|
94 | State machines execute using an IAM Role, which will automatically have all
|
95 | permissions added that are required to make all state machine tasks execute
|
96 | properly (for example, permissions to invoke any Lambda functions you add to
|
97 | your workflow). A role will be created by default, but you can supply an
|
98 | existing one as well.
|
99 |
|
100 | ## Amazon States Language
|
101 |
|
102 | This library comes with a set of classes that model the [Amazon States
|
103 | Language](https://states-language.net/spec.html). The following State classes
|
104 | are supported:
|
105 |
|
106 | * [`Task`](#task)
|
107 | * [`Pass`](#pass)
|
108 | * [`Wait`](#wait)
|
109 | * [`Choice`](#choice)
|
110 | * [`Parallel`](#parallel)
|
111 | * [`Succeed`](#succeed)
|
112 | * [`Fail`](#fail)
|
113 | * [`Map`](#map)
|
114 | * [`Custom State`](#custom-state)
|
115 |
|
116 | An arbitrary JSON object (specified at execution start) is passed from state to
|
117 | state and transformed during the execution of the workflow. For more
|
118 | information, see the States Language spec.
|
119 |
|
120 | ### Task
|
121 |
|
122 | A `Task` represents some work that needs to be done. The exact work to be
|
123 | done is determine by a class that implements `IStepFunctionsTask`, a collection
|
124 | of which can be found in the `@aws-cdk/aws-stepfunctions-tasks` module.
|
125 |
|
126 | The tasks in the `@aws-cdk/aws-stepfunctions-tasks` module support the
|
127 | [service integration pattern](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html) that integrates Step Functions with services
|
128 | directly in the Amazon States language.
|
129 |
|
130 | ### Pass
|
131 |
|
132 | A `Pass` state passes its input to its output, without performing work.
|
133 | Pass states are useful when constructing and debugging state machines.
|
134 |
|
135 | The following example injects some fixed data into the state machine through
|
136 | the `result` field. The `result` field will be added to the input and the result
|
137 | will be passed as the state's output.
|
138 |
|
139 | ```ts
|
140 | // Makes the current JSON state { ..., "subObject": { "hello": "world" } }
|
141 | const pass = new sfn.Pass(this, 'Add Hello World', {
|
142 | result: sfn.Result.fromObject({ hello: 'world' }),
|
143 | resultPath: '$.subObject',
|
144 | });
|
145 |
|
146 | // Set the next state
|
147 | pass.next(nextState);
|
148 | ```
|
149 |
|
150 | The `Pass` state also supports passing key-value pairs as input. Values can
|
151 | be static, or selected from the input with a path.
|
152 |
|
153 | The following example filters the `greeting` field from the state input
|
154 | and also injects a field called `otherData`.
|
155 |
|
156 | ```ts
|
157 | const pass = new sfn.Pass(this, 'Filter input and inject data', {
|
158 | parameters: { // input to the pass state
|
159 | input: sfn.JsonPath.stringAt('$.input.greeting'),
|
160 | otherData: 'some-extra-stuff'
|
161 | },
|
162 | });
|
163 | ```
|
164 |
|
165 | The object specified in `parameters` will be the input of the `Pass` state.
|
166 | Since neither `Result` nor `ResultPath` are supplied, the `Pass` state copies
|
167 | its input through to its output.
|
168 |
|
169 | Learn more about the [Pass state](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-pass-state.html)
|
170 |
|
171 | ### Wait
|
172 |
|
173 | A `Wait` state waits for a given number of seconds, or until the current time
|
174 | hits a particular time. The time to wait may be taken from the execution's JSON
|
175 | state.
|
176 |
|
177 | ```ts
|
178 | // Wait until it's the time mentioned in the the state object's "triggerTime"
|
179 | // field.
|
180 | const wait = new sfn.Wait(this, 'Wait For Trigger Time', {
|
181 | time: sfn.WaitTime.timestampPath('$.triggerTime'),
|
182 | });
|
183 |
|
184 | // Set the next state
|
185 | wait.next(startTheWork);
|
186 | ```
|
187 |
|
188 | ### Choice
|
189 |
|
190 | A `Choice` state can take a different path through the workflow based on the
|
191 | values in the execution's JSON state:
|
192 |
|
193 | ```ts
|
194 | const choice = new sfn.Choice(this, 'Did it work?');
|
195 |
|
196 | // Add conditions with .when()
|
197 | choice.when(sfn.Condition.stringEquals('$.status', 'SUCCESS'), successState);
|
198 | choice.when(sfn.Condition.numberGreaterThan('$.attempts', 5), failureState);
|
199 |
|
200 | // Use .otherwise() to indicate what should be done if none of the conditions match
|
201 | choice.otherwise(tryAgainState);
|
202 | ```
|
203 |
|
204 | If you want to temporarily branch your workflow based on a condition, but have
|
205 | all branches come together and continuing as one (similar to how an `if ...
|
206 | then ... else` works in a programming language), use the `.afterwards()` method:
|
207 |
|
208 | ```ts
|
209 | const choice = new sfn.Choice(this, 'What color is it?');
|
210 | choice.when(sfn.Condition.stringEquals('$.color', 'BLUE'), handleBlueItem);
|
211 | choice.when(sfn.Condition.stringEquals('$.color', 'RED'), handleRedItem);
|
212 | choice.otherwise(handleOtherItemColor);
|
213 |
|
214 | // Use .afterwards() to join all possible paths back together and continue
|
215 | choice.afterwards().next(shipTheItem);
|
216 | ```
|
217 |
|
218 | If your `Choice` doesn't have an `otherwise()` and none of the conditions match
|
219 | the JSON state, a `NoChoiceMatched` error will be thrown. Wrap the state machine
|
220 | in a `Parallel` state if you want to catch and recover from this.
|
221 |
|
222 | #### Available Conditions
|
223 |
|
224 | see [step function comparison operators](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-choice-state.html#amazon-states-language-choice-state-rules)
|
225 |
|
226 | * `Condition.isPresent` - matches if a json path is present
|
227 | * `Condition.isNotPresent` - matches if a json path is not present
|
228 | * `Condition.isString` - matches if a json path contains a string
|
229 | * `Condition.isNotString` - matches if a json path is not a string
|
230 | * `Condition.isNumeric` - matches if a json path is numeric
|
231 | * `Condition.isNotNumeric` - matches if a json path is not numeric
|
232 | * `Condition.isBoolean` - matches if a json path is boolean
|
233 | * `Condition.isNotBoolean` - matches if a json path is not boolean
|
234 | * `Condition.isTimestamp` - matches if a json path is a timestamp
|
235 | * `Condition.isNotTimestamp` - matches if a json path is not a timestamp
|
236 | * `Condition.isNotNull` - matches if a json path is not null
|
237 | * `Condition.isNull` - matches if a json path is null
|
238 | * `Condition.booleanEquals` - matches if a boolean field has a given value
|
239 | * `Condition.booleanEqualsJsonPath` - matches if a boolean field equals a value in a given mapping path
|
240 | * `Condition.stringEqualsJsonPath` - matches if a string field equals a given mapping path
|
241 | * `Condition.stringEquals` - matches if a field equals a string value
|
242 | * `Condition.stringLessThan` - matches if a string field sorts before a given value
|
243 | * `Condition.stringLessThanJsonPath` - matches if a string field sorts before a value at given mapping path
|
244 | * `Condition.stringLessThanEquals` - matches if a string field sorts equal to or before a given value
|
245 | * `Condition.stringLessThanEqualsJsonPath` - matches if a string field sorts equal to or before a given mapping
|
246 | * `Condition.stringGreaterThan` - matches if a string field sorts after a given value
|
247 | * `Condition.stringGreaterThanJsonPath` - matches if a string field sorts after a value at a given mapping path
|
248 | * `Condition.stringGreaterThanEqualsJsonPath` - matches if a string field sorts after or equal to value at a given mapping path
|
249 | * `Condition.stringGreaterThanEquals` - matches if a string field sorts after or equal to a given value
|
250 | * `Condition.numberEquals` - matches if a numeric field has the given value
|
251 | * `Condition.numberEqualsJsonPath` - matches if a numeric field has the value in a given mapping path
|
252 | * `Condition.numberLessThan` - matches if a numeric field is less than the given value
|
253 | * `Condition.numberLessThanJsonPath` - matches if a numeric field is less than the value at the given mapping path
|
254 | * `Condition.numberLessThanEquals` - matches if a numeric field is less than or equal to the given value
|
255 | * `Condition.numberLessThanEqualsJsonPath` - matches if a numeric field is less than or equal to the numeric value at given mapping path
|
256 | * `Condition.numberGreaterThan` - matches if a numeric field is greater than the given value
|
257 | * `Condition.numberGreaterThanJsonPath` - matches if a numeric field is greater than the value at a given mapping path
|
258 | * `Condition.numberGreaterThanEquals` - matches if a numeric field is greater than or equal to the given value
|
259 | * `Condition.numberGreaterThanEqualsJsonPath` - matches if a numeric field is greater than or equal to the value at a given mapping path
|
260 | * `Condition.timestampEquals` - matches if a timestamp field is the same time as the given timestamp
|
261 | * `Condition.timestampEqualsJsonPath` - matches if a timestamp field is the same time as the timestamp at a given mapping path
|
262 | * `Condition.timestampLessThan` - matches if a timestamp field is before the given timestamp
|
263 | * `Condition.timestampLessThanJsonPath` - matches if a timestamp field is before the timestamp at a given mapping path
|
264 | * `Condition.timestampLessThanEquals` - matches if a timestamp field is before or equal to the given timestamp
|
265 | * `Condition.timestampLessThanEqualsJsonPath` - matches if a timestamp field is before or equal to the timestamp at a given mapping path
|
266 | * `Condition.timestampGreaterThan` - matches if a timestamp field is after the timestamp at a given mapping path
|
267 | * `Condition.timestampGreaterThanJsonPath` - matches if a timestamp field is after the timestamp at a given mapping path
|
268 | * `Condition.timestampGreaterThanEquals` - matches if a timestamp field is after or equal to the given timestamp
|
269 | * `Condition.timestampGreaterThanEqualsJsonPath` - matches if a timestamp field is after or equal to the timestamp at a given mapping path
|
270 | * `Condition.stringMatches` - matches if a field matches a string pattern that can contain a wild card (\*) e.g: log-\*.txt or \*LATEST\*. No other characters other than "\*" have any special meaning - \* can be escaped: \\\\*
|
271 |
|
272 | ### Parallel
|
273 |
|
274 | A `Parallel` state executes one or more subworkflows in parallel. It can also
|
275 | be used to catch and recover from errors in subworkflows.
|
276 |
|
277 | ```ts
|
278 | const parallel = new sfn.Parallel(this, 'Do the work in parallel');
|
279 |
|
280 | // Add branches to be executed in parallel
|
281 | parallel.branch(shipItem);
|
282 | parallel.branch(sendInvoice);
|
283 | parallel.branch(restock);
|
284 |
|
285 | // Retry the whole workflow if something goes wrong
|
286 | parallel.addRetry({ maxAttempts: 1 });
|
287 |
|
288 | // How to recover from errors
|
289 | parallel.addCatch(sendFailureNotification);
|
290 |
|
291 | // What to do in case everything succeeded
|
292 | parallel.next(closeOrder);
|
293 | ```
|
294 |
|
295 | ### Succeed
|
296 |
|
297 | Reaching a `Succeed` state terminates the state machine execution with a
|
298 | succesful status.
|
299 |
|
300 | ```ts
|
301 | const success = new sfn.Succeed(this, 'We did it!');
|
302 | ```
|
303 |
|
304 | ### Fail
|
305 |
|
306 | Reaching a `Fail` state terminates the state machine execution with a
|
307 | failure status. The fail state should report the reason for the failure.
|
308 | Failures can be caught by encompassing `Parallel` states.
|
309 |
|
310 | ```ts
|
311 | const success = new sfn.Fail(this, 'Fail', {
|
312 | error: 'WorkflowFailure',
|
313 | cause: "Something went wrong"
|
314 | });
|
315 | ```
|
316 |
|
317 | ### Map
|
318 |
|
319 | A `Map` state can be used to run a set of steps for each element of an input array.
|
320 | A `Map` state will execute the same steps for multiple entries of an array in the state input.
|
321 |
|
322 | While the `Parallel` state executes multiple branches of steps using the same input, a `Map` state will
|
323 | execute the same steps for multiple entries of an array in the state input.
|
324 |
|
325 | ```ts
|
326 | const map = new sfn.Map(this, 'Map State', {
|
327 | maxConcurrency: 1,
|
328 | itemsPath: sfn.JsonPath.stringAt('$.inputForMap')
|
329 | });
|
330 | map.iterator(new sfn.Pass(this, 'Pass State'));
|
331 | ```
|
332 |
|
333 | ### Custom State
|
334 |
|
335 | It's possible that the high-level constructs for the states or `stepfunctions-tasks` do not have
|
336 | the states or service integrations you are looking for. The primary reasons for this lack of
|
337 | functionality are:
|
338 |
|
339 | * 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
|
340 | classes in the CDK.
|
341 | * The state or state properties are available through Step Functions, but are not configurable
|
342 | through constructs
|
343 |
|
344 | If a feature is not available, a `CustomState` can be used to supply any Amazon States Language
|
345 | JSON-based object as the state definition.
|
346 |
|
347 | [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.
|
348 |
|
349 | Custom states can be chained together with any of the other states to create your state machine
|
350 | definition. You will also need to provide any permissions that are required to the `role` that
|
351 | the State Machine uses.
|
352 |
|
353 | The following example uses the `DynamoDB` service integration to insert data into a DynamoDB table.
|
354 |
|
355 | ```ts
|
356 | import * as ddb from '@aws-cdk/aws-dynamodb';
|
357 | import * as cdk from '@aws-cdk/core';
|
358 | import * as sfn from '@aws-cdk/aws-stepfunctions';
|
359 |
|
360 | // create a table
|
361 | const table = new ddb.Table(this, 'montable', {
|
362 | partitionKey: {
|
363 | name: 'id',
|
364 | type: ddb.AttributeType.STRING,
|
365 | },
|
366 | });
|
367 |
|
368 | const finalStatus = new sfn.Pass(stack, 'final step');
|
369 |
|
370 | // States language JSON to put an item into DynamoDB
|
371 | // snippet generated from https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-code-snippet.html#tutorial-code-snippet-1
|
372 | const stateJson = {
|
373 | Type: 'Task',
|
374 | Resource: 'arn:aws:states:::dynamodb:putItem',
|
375 | Parameters: {
|
376 | TableName: table.tableName,
|
377 | Item: {
|
378 | id: {
|
379 | S: 'MyEntry',
|
380 | },
|
381 | },
|
382 | },
|
383 | ResultPath: null,
|
384 | };
|
385 |
|
386 | // custom state which represents a task to insert data into DynamoDB
|
387 | const custom = new sfn.CustomState(this, 'my custom task', {
|
388 | stateJson,
|
389 | });
|
390 |
|
391 | const chain = sfn.Chain.start(custom)
|
392 | .next(finalStatus);
|
393 |
|
394 | const sm = new sfn.StateMachine(this, 'StateMachine', {
|
395 | definition: chain,
|
396 | timeout: cdk.Duration.seconds(30),
|
397 | });
|
398 |
|
399 | // don't forget permissions. You need to assign them
|
400 | table.grantWriteData(sm);
|
401 | ```
|
402 |
|
403 | ## Task Chaining
|
404 |
|
405 | To make defining work flows as convenient (and readable in a top-to-bottom way)
|
406 | as writing regular programs, it is possible to chain most methods invocations.
|
407 | In particular, the `.next()` method can be repeated. The result of a series of
|
408 | `.next()` calls is called a **Chain**, and can be used when defining the jump
|
409 | targets of `Choice.on` or `Parallel.branch`:
|
410 |
|
411 | ```ts
|
412 | const definition = step1
|
413 | .next(step2)
|
414 | .next(choice
|
415 | .when(condition1, step3.next(step4).next(step5))
|
416 | .otherwise(step6)
|
417 | .afterwards())
|
418 | .next(parallel
|
419 | .branch(step7.next(step8))
|
420 | .branch(step9.next(step10)))
|
421 | .next(finish);
|
422 |
|
423 | new sfn.StateMachine(this, 'StateMachine', {
|
424 | definition,
|
425 | });
|
426 | ```
|
427 |
|
428 | If you don't like the visual look of starting a chain directly off the first
|
429 | step, you can use `Chain.start`:
|
430 |
|
431 | ```ts
|
432 | const definition = sfn.Chain
|
433 | .start(step1)
|
434 | .next(step2)
|
435 | .next(step3)
|
436 | // ...
|
437 | ```
|
438 |
|
439 | ## State Machine Fragments
|
440 |
|
441 | It is possible to define reusable (or abstracted) mini-state machines by
|
442 | defining a construct that implements `IChainable`, which requires you to define
|
443 | two fields:
|
444 |
|
445 | * `startState: State`, representing the entry point into this state machine.
|
446 | * `endStates: INextable[]`, representing the (one or more) states that outgoing
|
447 | transitions will be added to if you chain onto the fragment.
|
448 |
|
449 | Since states will be named after their construct IDs, you may need to prefix the
|
450 | IDs of states if you plan to instantiate the same state machine fragment
|
451 | multiples times (otherwise all states in every instantiation would have the same
|
452 | name).
|
453 |
|
454 | The class `StateMachineFragment` contains some helper functions (like
|
455 | `prefixStates()`) to make it easier for you to do this. If you define your state
|
456 | machine as a subclass of this, it will be convenient to use:
|
457 |
|
458 | ```ts
|
459 | interface MyJobProps {
|
460 | jobFlavor: string;
|
461 | }
|
462 |
|
463 | class MyJob extends sfn.StateMachineFragment {
|
464 | public readonly startState: sfn.State;
|
465 | public readonly endStates: sfn.INextable[];
|
466 |
|
467 | constructor(parent: cdk.Construct, id: string, props: MyJobProps) {
|
468 | super(parent, id);
|
469 |
|
470 | const first = new sfn.Task(this, 'First', { ... });
|
471 | // ...
|
472 | const last = new sfn.Task(this, 'Last', { ... });
|
473 |
|
474 | this.startState = first;
|
475 | this.endStates = [last];
|
476 | }
|
477 | }
|
478 |
|
479 | // Do 3 different variants of MyJob in parallel
|
480 | new sfn.Parallel(this, 'All jobs')
|
481 | .branch(new MyJob(this, 'Quick', { jobFlavor: 'quick' }).prefixStates())
|
482 | .branch(new MyJob(this, 'Medium', { jobFlavor: 'medium' }).prefixStates())
|
483 | .branch(new MyJob(this, 'Slow', { jobFlavor: 'slow' }).prefixStates());
|
484 | ```
|
485 |
|
486 | A few utility functions are available to parse state machine fragments.
|
487 |
|
488 | * `State.findReachableStates`: Retrieve the list of states reachable from a given state.
|
489 | * `State.findReachableEndStates`: Retrieve the list of end or terminal states reachable from a given state.
|
490 |
|
491 | ## Activity
|
492 |
|
493 | **Activities** represent work that is done on some non-Lambda worker pool. The
|
494 | Step Functions workflow will submit work to this Activity, and a worker pool
|
495 | that you run yourself, probably on EC2, will pull jobs from the Activity and
|
496 | submit the results of individual jobs back.
|
497 |
|
498 | You need the ARN to do so, so if you use Activities be sure to pass the Activity
|
499 | ARN into your worker pool:
|
500 |
|
501 | ```ts
|
502 | const activity = new sfn.Activity(this, 'Activity');
|
503 |
|
504 | // Read this CloudFormation Output from your application and use it to poll for work on
|
505 | // the activity.
|
506 | new cdk.CfnOutput(this, 'ActivityArn', { value: activity.activityArn });
|
507 | ```
|
508 |
|
509 | ### Activity-Level Permissions
|
510 |
|
511 | Granting IAM permissions to an activity can be achieved by calling the `grant(principal, actions)` API:
|
512 |
|
513 | ```ts
|
514 | const activity = new sfn.Activity(this, 'Activity');
|
515 |
|
516 | const role = new iam.Role(stack, 'Role', {
|
517 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
518 | });
|
519 |
|
520 | activity.grant(role, 'states:SendTaskSuccess');
|
521 | ```
|
522 |
|
523 | This will grant the IAM principal the specified actions onto the activity.
|
524 |
|
525 | ## Metrics
|
526 |
|
527 | `Task` object expose various metrics on the execution of that particular task. For example,
|
528 | to create an alarm on a particular task failing:
|
529 |
|
530 | ```ts
|
531 | new cloudwatch.Alarm(this, 'TaskAlarm', {
|
532 | metric: task.metricFailed(),
|
533 | threshold: 1,
|
534 | evaluationPeriods: 1,
|
535 | });
|
536 | ```
|
537 |
|
538 | There are also metrics on the complete state machine:
|
539 |
|
540 | ```ts
|
541 | new cloudwatch.Alarm(this, 'StateMachineAlarm', {
|
542 | metric: stateMachine.metricFailed(),
|
543 | threshold: 1,
|
544 | evaluationPeriods: 1,
|
545 | });
|
546 | ```
|
547 |
|
548 | And there are metrics on the capacity of all state machines in your account:
|
549 |
|
550 | ```ts
|
551 | new cloudwatch.Alarm(this, 'ThrottledAlarm', {
|
552 | metric: StateTransitionMetrics.metricThrottledEvents(),
|
553 | threshold: 10,
|
554 | evaluationPeriods: 2,
|
555 | });
|
556 | ```
|
557 |
|
558 | ## Logging
|
559 |
|
560 | Enable logging to CloudWatch by passing a logging configuration with a
|
561 | destination LogGroup:
|
562 |
|
563 | ```ts
|
564 | const logGroup = new logs.LogGroup(stack, 'MyLogGroup');
|
565 |
|
566 | new sfn.StateMachine(stack, 'MyStateMachine', {
|
567 | definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')),
|
568 | logs: {
|
569 | destination: logGroup,
|
570 | level: sfn.LogLevel.ALL,
|
571 | }
|
572 | });
|
573 | ```
|
574 |
|
575 | ## X-Ray tracing
|
576 |
|
577 | Enable X-Ray tracing for StateMachine:
|
578 |
|
579 | ```ts
|
580 | const logGroup = new logs.LogGroup(stack, 'MyLogGroup');
|
581 |
|
582 | new sfn.StateMachine(stack, 'MyStateMachine', {
|
583 | definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')),
|
584 | tracingEnabled: true
|
585 | });
|
586 | ```
|
587 |
|
588 | See [the AWS documentation](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-xray-tracing.html)
|
589 | to learn more about AWS Step Functions's X-Ray support.
|
590 |
|
591 | ## State Machine Permission Grants
|
592 |
|
593 | IAM roles, users, or groups which need to be able to work with a State Machine should be granted IAM permissions.
|
594 |
|
595 | Any object that implements the `IGrantable` interface (has an associated principal) can be granted permissions by calling:
|
596 |
|
597 | * `stateMachine.grantStartExecution(principal)` - grants the principal the ability to execute the state machine
|
598 | * `stateMachine.grantRead(principal)` - grants the principal read access
|
599 | * `stateMachine.grantTaskResponse(principal)` - grants the principal the ability to send task tokens to the state machine
|
600 | * `stateMachine.grantExecution(principal, actions)` - grants the principal execution-level permissions for the IAM actions specified
|
601 | * `stateMachine.grant(principal, actions)` - grants the principal state-machine-level permissions for the IAM actions specified
|
602 |
|
603 | ### Start Execution Permission
|
604 |
|
605 | Grant permission to start an execution of a state machine by calling the `grantStartExecution()` API.
|
606 |
|
607 | ```ts
|
608 | const role = new iam.Role(stack, 'Role', {
|
609 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
610 | });
|
611 |
|
612 | const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
|
613 | definition,
|
614 | });
|
615 |
|
616 | // Give role permission to start execution of state machine
|
617 | stateMachine.grantStartExecution(role);
|
618 | ```
|
619 |
|
620 | The following permission is provided to a service principal by the `grantStartExecution()` API:
|
621 |
|
622 | * `states:StartExecution` - to state machine
|
623 |
|
624 | ### Read Permissions
|
625 |
|
626 | Grant `read` access to a state machine by calling the `grantRead()` API.
|
627 |
|
628 | ```ts
|
629 | const role = new iam.Role(stack, 'Role', {
|
630 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
631 | });
|
632 |
|
633 | const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
|
634 | definition,
|
635 | });
|
636 |
|
637 | // Give role read access to state machine
|
638 | stateMachine.grantRead(role);
|
639 | ```
|
640 |
|
641 | The following read permissions are provided to a service principal by the `grantRead()` API:
|
642 |
|
643 | * `states:ListExecutions` - to state machine
|
644 | * `states:ListStateMachines` - to state machine
|
645 | * `states:DescribeExecution` - to executions
|
646 | * `states:DescribeStateMachineForExecution` - to executions
|
647 | * `states:GetExecutionHistory` - to executions
|
648 | * `states:ListActivities` - to `*`
|
649 | * `states:DescribeStateMachine` - to `*`
|
650 | * `states:DescribeActivity` - to `*`
|
651 |
|
652 | ### Task Response Permissions
|
653 |
|
654 | Grant permission to allow task responses to a state machine by calling the `grantTaskResponse()` API:
|
655 |
|
656 | ```ts
|
657 | const role = new iam.Role(stack, 'Role', {
|
658 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
659 | });
|
660 |
|
661 | const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
|
662 | definition,
|
663 | });
|
664 |
|
665 | // Give role task response permissions to the state machine
|
666 | stateMachine.grantTaskResponse(role);
|
667 | ```
|
668 |
|
669 | The following read permissions are provided to a service principal by the `grantRead()` API:
|
670 |
|
671 | * `states:SendTaskSuccess` - to state machine
|
672 | * `states:SendTaskFailure` - to state machine
|
673 | * `states:SendTaskHeartbeat` - to state machine
|
674 |
|
675 | ### Execution-level Permissions
|
676 |
|
677 | Grant execution-level permissions to a state machine by calling the `grantExecution()` API:
|
678 |
|
679 | ```ts
|
680 | const role = new iam.Role(stack, 'Role', {
|
681 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
682 | });
|
683 |
|
684 | const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
|
685 | definition,
|
686 | });
|
687 |
|
688 | // Give role permission to get execution history of ALL executions for the state machine
|
689 | stateMachine.grantExecution(role, 'states:GetExecutionHistory');
|
690 | ```
|
691 |
|
692 | ### Custom Permissions
|
693 |
|
694 | You can add any set of permissions to a state machine by calling the `grant()` API.
|
695 |
|
696 | ```ts
|
697 | const user = new iam.User(stack, 'MyUser');
|
698 |
|
699 | const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
|
700 | definition,
|
701 | });
|
702 |
|
703 | //give user permission to send task success to the state machine
|
704 | stateMachine.grant(user, 'states:SendTaskSuccess');
|
705 | ```
|
706 |
|
707 | ## Import
|
708 |
|
709 | Any Step Functions state machine that has been created outside the stack can be imported
|
710 | into your CDK stack.
|
711 |
|
712 | State machines can be imported by their ARN via the `StateMachine.fromStateMachineArn()` API
|
713 |
|
714 | ```ts
|
715 | import * as sfn from 'aws-stepfunctions';
|
716 |
|
717 | const stack = new Stack(app, 'MyStack');
|
718 | sfn.StateMachine.fromStateMachineArn(
|
719 | stack,
|
720 | 'ImportedStateMachine',
|
721 | 'arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ');
|
722 | ```
|