Skip to content

Commit

Permalink
feat(Typescript): StepFunctions CRON example (#336)
Browse files Browse the repository at this point in the history
* Add step functions cron example for typescript

* Update message

* Update deprecated code

* Fix wrong name

* Update stepfunctions-job-poller example with cron job

* fix: reduced wait `300` -> `30` to prevent timeout, clean spacing, added graph and steps to README

* revert rename of `README` -> `REAMDE`

Co-authored-by: Ryan Parker <[email protected]>
  • Loading branch information
michchan and ryparker authored Jul 9, 2021
1 parent 2933fd4 commit 9bf9953
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 73 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[.py]
indent_size = 4
32 changes: 25 additions & 7 deletions typescript/stepfunctions-job-poller/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
# Stepfunctions Job Poller
# Step Functions Job Poller

<!--BEGIN STABILITY BANNER-->
---

![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)

> **This is an experimental example. It may not build out of the box**
>
> This examples does is built on Construct Libraries marked "Experimental" and may not be updated for latest breaking changes.
> This examples is built on Construct Libraries marked "Experimental" and may not be updated for latest breaking changes.
>
> If build is unsuccessful, please create an [issue](https://github.com/aws-samples/aws-cdk-examples/issues/new) so that we may debug the problem
> If build is unsuccessful, please create an [issue](https://github.com/aws-samples/aws-cdk-examples/issues/new) so that we may debug the problem
---
<!--END STABILITY BANNER-->

This example creates a basic Stepfunction workflow that starts a task and then listens at regular intervals for completion, then reports completion and status.
This example creates a basic Step Function workflow that starts a task and then listens at regular intervals for completion, then reports completion and status.

<kbd>
<img src="./step-function-graph.png" width="500px" margin="auto" />
</kbd>

Tasks:

1. `Start`
2. `Submit Lambda`: Invoke Submit Lambda which sets status
3. `Wait X Seconds`: Wait
4. `Get Job Status`: Invoke Check Status Lambda which validates status
5. `Job Complete?`: Choice: Read `"status"` of payload
a. If `"status"` is `"SUCCEEDED"` then continue to `#7 Get Final Job Status` task
b. If `"status"` is `"FAILED"` then continue to `#6 Job Failed` task
c. else return to task `#3 Wait X Seconds`
6. `Job Failed`: Define a Fail state in the state machine.
7. `Get Final Job Status`: Invoke Check Status Lambda
8. `End`

## Build

To build this app, you need to be in this example's root folder. Then run the following:

```bash
npm install -g aws-cdk
npm install
npm run build
$ npm install -g aws-cdk
$ npm install
$ npm run build
```

This will install the necessary CDK, then this example's dependencies, and then build your TypeScript files and your CloudFormation template.
Expand Down
131 changes: 86 additions & 45 deletions typescript/stepfunctions-job-poller/index.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,91 @@
import cdk = require('@aws-cdk/core');
import sfn = require('@aws-cdk/aws-stepfunctions');
import sfn_tasks = require('@aws-cdk/aws-stepfunctions-tasks');
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';
import * as events from '@aws-cdk/aws-events';
import * as targets from '@aws-cdk/aws-events-targets';
import * as fs from 'fs'

class JobPollerStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) {
super(scope, id, props);

const submitJobActivity = new sfn.Activity(this, 'SubmitJob');
const checkJobActivity = new sfn.Activity(this, 'CheckJob');

const submitJob = new sfn.Task(this, 'Submit Job', {
task: new sfn_tasks.InvokeActivity(submitJobActivity),
resultPath: '$.guid',
});
const waitX = new sfn.Wait(this, 'Wait X Seconds', {
time: sfn.WaitTime.secondsPath('$.wait_time')
});
const getStatus = new sfn.Task(this, 'Get Job Status', {
task: new sfn_tasks.InvokeActivity(checkJobActivity),
inputPath: '$.guid',
resultPath: '$.status',
});
const isComplete = new sfn.Choice(this, 'Job Complete?');
const jobFailed = new sfn.Fail(this, 'Job Failed', {
cause: 'AWS Batch Job Failed',
error: 'DescribeJob returned FAILED',
});
const finalStatus = new sfn.Task(this, 'Get Final Job Status', {
task: new sfn_tasks.InvokeActivity(checkJobActivity),
inputPath: '$.guid',
});

const chain = sfn.Chain
.start(submitJob)
.next(waitX)
.next(getStatus)
.next(isComplete
.when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
.when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus)
.otherwise(waitX));

new sfn.StateMachine(this, 'StateMachine', {
definition: chain,
timeout: cdk.Duration.seconds(30)
});
}
constructor(app: cdk.App, id: string) {
super(app, id);

/** ------------------ Lambda Handlers Definition ------------------ */

const getStatusLambda = new lambda.Function(this, 'CheckLambda', {
code: new lambda.InlineCode(fs.readFileSync('lambdas/check_status.py', { encoding: 'utf-8' })),
handler: 'index.main',
timeout: cdk.Duration.seconds(30),
runtime: lambda.Runtime.PYTHON_3_6,
});

const submitLambda = new lambda.Function(this, 'SubmitLambda', {
code: new lambda.InlineCode(fs.readFileSync('lambdas/submit.py', { encoding: 'utf-8' })),
handler: 'index.main',
timeout: cdk.Duration.seconds(30),
runtime: lambda.Runtime.PYTHON_3_6,
});

/** ------------------ Step functions Definition ------------------ */

const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', {
lambdaFunction: submitLambda,
// Lambda's result is in the attribute `Payload`
outputPath: '$.Payload',
});
const waitX = new sfn.Wait(this, 'Wait X Seconds', {
/**
* You can also implement with the path stored in the state like:
* sfn.WaitTime.secondsPath('$.waitSeconds')
*/
time: sfn.WaitTime.duration(cdk.Duration.seconds(30)),
});
const getStatus = new tasks.LambdaInvoke(this, 'Get Job Status', {
lambdaFunction: getStatusLambda,
outputPath: '$.Payload',
});

const jobFailed = new sfn.Fail(this, 'Job Failed', {
cause: 'AWS Batch Job Failed',
error: 'DescribeJob returned FAILED',
});

const finalStatus = new tasks.LambdaInvoke(this, 'Get Final Job Status', {
lambdaFunction: getStatusLambda,
outputPath: '$.Payload',
});

// Create chain
const definition = submitJob
.next(waitX)
.next(getStatus)
.next(new sfn.Choice(this, 'Job Complete?')
// Look at the "status" field
.when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
.when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus)
.otherwise(waitX));

// Create state machine
const stateMachine = new sfn.StateMachine(this, 'CronStateMachine', {
definition,
timeout: cdk.Duration.minutes(5),
});

// Grant lambda execution roles
submitLambda.grantInvoke(stateMachine.role);
getStatusLambda.grantInvoke(stateMachine.role);

/** ------------------ Events Rule Definition ------------------ */

/**
* Run every day at 6PM UTC
* See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html
*/
const rule = new events.Rule(this, 'Rule', {
schedule: events.Schedule.expression('cron(0 18 ? * MON-FRI *)')
});
rule.addTarget(new targets.SfnStateMachine(stateMachine));
}
}

const app = new cdk.App();
Expand Down
5 changes: 5 additions & 0 deletions typescript/stepfunctions-job-poller/lambdas/check_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def main(event, context):
if event["status"] == "SUCCEEDED":
return {"status": "SUCCEEDED", "id": event["id"]}
else:
return {"status": "FAILED", "id": event["id"]}
7 changes: 7 additions & 0 deletions typescript/stepfunctions-job-poller/lambdas/submit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def main(event, context):
print('The job is submitted successfully!')
# Return the handling result
return {
"id": event['id'],
"status": "SUCCEEDED",
}
7 changes: 5 additions & 2 deletions typescript/stepfunctions-job-poller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^10.17.0",
"typescript": "~3.7.2"
"@types/node": "*",
"typescript": "^3.8"
},
"dependencies": {
"@aws-cdk/aws-events": "*",
"@aws-cdk/aws-events-targets": "*",
"@aws-cdk/aws-lambda": "*",
"@aws-cdk/aws-stepfunctions": "*",
"@aws-cdk/aws-stepfunctions-tasks": "*",
"@aws-cdk/core": "*"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 25 additions & 19 deletions typescript/stepfunctions-job-poller/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
{
"compilerOptions": {
"target":"ES2018",
"module": "commonjs",
"lib": ["es2016", "es2017.object", "es2017.string"],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization":false
}
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": [
"es2016",
"es2017.object",
"es2017.string"
],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false
},
"exclude": [
"cdk.out"
]
}

0 comments on commit 9bf9953

Please sign in to comment.