forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(stepfunctions): add
EvaluateExpression
task (aws#4602)
Use the EvaluateExpression to perform simple operations referencing state paths. The expression referenced in the task will be evaluated in a Lambda function (eval()). This allows you to not have to write Lambda code for simple operations.
- Loading branch information
Showing
8 changed files
with
486 additions
and
2 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
packages/@aws-cdk/aws-stepfunctions-tasks/lib/eval-nodejs10.x-handler/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// tslint:disable:no-console no-eval | ||
import { Event } from '../evaluate-expression'; | ||
|
||
export async function handler(event: Event): Promise<any> { | ||
console.log('Event: %j', event); | ||
|
||
const expression = Object.entries(event.expressionAttributeValues) | ||
.reduce( | ||
(exp, [k, v]) => exp.replace(k, JSON.stringify(v)), | ||
event.expression | ||
); | ||
console.log(`Expression: ${expression}`); | ||
|
||
return eval(expression); | ||
} |
104 changes: 104 additions & 0 deletions
104
packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import iam = require('@aws-cdk/aws-iam'); | ||
import lambda = require('@aws-cdk/aws-lambda'); | ||
import sfn = require('@aws-cdk/aws-stepfunctions'); | ||
import cdk = require('@aws-cdk/core'); | ||
import path = require('path'); | ||
|
||
/** | ||
* Properties for EvaluateExpression | ||
* | ||
* @experimental | ||
*/ | ||
export interface EvaluateExpressionProps { | ||
/** | ||
* The expression to evaluate. It must contain state paths. | ||
* | ||
* @example '$.a + $.b' | ||
*/ | ||
readonly expression: string; | ||
|
||
/** | ||
* The runtime language to use to evaluate the expression. | ||
* | ||
* @default lambda.Runtime.NODEJS_10_X | ||
*/ | ||
readonly runtime?: lambda.Runtime; | ||
} | ||
|
||
/** | ||
* The event received by the Lambda function | ||
* | ||
* @internal | ||
*/ | ||
export interface Event { | ||
/** | ||
* The expression to evaluate | ||
*/ | ||
readonly expression: string; | ||
|
||
/** | ||
* The expression attribute values | ||
*/ | ||
readonly expressionAttributeValues: { [key: string]: any }; | ||
} | ||
|
||
/** | ||
* A Step Functions Task to evaluate an expression | ||
* | ||
* OUTPUT: the output of this task is the evaluated expression. | ||
* | ||
* @experimental | ||
*/ | ||
export class EvaluateExpression implements sfn.IStepFunctionsTask { | ||
constructor(private readonly props: EvaluateExpressionProps) { | ||
} | ||
|
||
public bind(task: sfn.Task): sfn.StepFunctionsTaskConfig { | ||
const matches = this.props.expression.match(/\$[.\[][.a-zA-Z[\]0-9]+/g); | ||
|
||
if (!matches) { | ||
throw new Error('No paths found in expression'); | ||
} | ||
|
||
const expressionAttributeValues = matches.reduce( | ||
(acc, m) => ({ | ||
...acc, | ||
[m]: sfn.Data.stringAt(m) // It's okay to always use `stringAt` here | ||
}), | ||
{} | ||
); | ||
|
||
const evalFn = createEvalFn(this.props.runtime || lambda.Runtime.NODEJS_10_X, task); | ||
|
||
return { | ||
resourceArn: evalFn.functionArn, | ||
policyStatements: [new iam.PolicyStatement({ | ||
resources: [evalFn.functionArn], | ||
actions: ['lambda:InvokeFunction'], | ||
})], | ||
parameters: { | ||
expression: this.props.expression, | ||
expressionAttributeValues, | ||
} as Event | ||
}; | ||
} | ||
} | ||
|
||
function createEvalFn(runtime: lambda.Runtime, scope: cdk.Construct) { | ||
const code = lambda.Code.asset(path.join(__dirname, `eval-${runtime.name}-handler`)); | ||
const lambdaPurpose = 'Eval'; | ||
|
||
switch (runtime) { | ||
case lambda.Runtime.NODEJS_10_X: | ||
return new lambda.SingletonFunction(scope, 'EvalFunction', { | ||
runtime, | ||
handler: 'index.handler', | ||
uuid: 'a0d2ce44-871b-4e74-87a1-f5e63d7c3bdc', | ||
lambdaPurpose, | ||
code, | ||
}); | ||
// TODO: implement other runtimes | ||
default: | ||
throw new Error(`The runtime ${runtime.name} is currently not supported.`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
packages/@aws-cdk/aws-stepfunctions-tasks/test/eval-nodejs10.x-handler.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Event } from '../lib'; | ||
import { handler } from '../lib/eval-nodejs10.x-handler'; | ||
|
||
test('with numbers', async () => { | ||
// GIVEN | ||
const event: Event = { | ||
expression: '$.a + $.b', | ||
expressionAttributeValues: { | ||
'$.a': 4, | ||
'$.b': 5 | ||
} | ||
}; | ||
|
||
// THEN | ||
const evaluated = await handler(event); | ||
expect(evaluated).toBe(9); | ||
}); | ||
|
||
test('with strings', async () => { | ||
// GIVEN | ||
const event: Event = { | ||
expression: '`${$.a} ${$.b}`', | ||
expressionAttributeValues: { | ||
'$.a': 'Hello', | ||
'$.b': 'world!' | ||
} | ||
}; | ||
|
||
// THEN | ||
const evaluated = await handler(event); | ||
expect(evaluated).toBe('Hello world!'); | ||
}); | ||
|
||
test('with lists', async () => { | ||
// GIVEN | ||
const event: Event = { | ||
expression: '$.a.map(x => x * 2)', | ||
expressionAttributeValues: { | ||
'$.a': [1, 2, 3], | ||
} | ||
}; | ||
|
||
// THEN | ||
const evaluated = await handler(event); | ||
expect(evaluated).toEqual([2, 4, 6]); | ||
}); |
53 changes: 53 additions & 0 deletions
53
packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import '@aws-cdk/assert/jest'; | ||
import sfn = require('@aws-cdk/aws-stepfunctions'); | ||
import { Stack } from '@aws-cdk/core'; | ||
import tasks = require('../lib'); | ||
|
||
let stack: Stack; | ||
beforeEach(() => { | ||
stack = new Stack(); | ||
}); | ||
|
||
test('Eval with Node.js', () => { | ||
// WHEN | ||
const task = new sfn.Task(stack, 'Task', { | ||
task: new tasks.EvaluateExpression({ | ||
expression: '$.a + $.b', | ||
}) | ||
}); | ||
new sfn.StateMachine(stack, 'SM', { | ||
definition: task | ||
}); | ||
|
||
// THEN | ||
expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { | ||
DefinitionString: { | ||
"Fn::Join": [ | ||
"", | ||
[ | ||
"{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"expression\":\"$.a + $.b\",\"expressionAttributeValues\":{\"$.a.$\":\"$.a\",\"$.b.$\":\"$.b\"}},\"Type\":\"Task\",\"Resource\":\"", | ||
{ | ||
"Fn::GetAtt": [ | ||
"Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", | ||
"Arn" | ||
] | ||
}, | ||
"\"}}}" | ||
] | ||
] | ||
}, | ||
}); | ||
|
||
expect(stack).toHaveResource('AWS::Lambda::Function', { | ||
Runtime: 'nodejs10.x' | ||
}); | ||
}); | ||
|
||
test('Throws when expression does not contain paths', () => { | ||
// WHEN | ||
expect(() => new sfn.Task(stack, 'Task', { | ||
task: new tasks.EvaluateExpression({ | ||
expression: '2 + 2', | ||
}) | ||
})).toThrow(/No paths found in expression/); | ||
}); |
Oops, something went wrong.