Skip to content

Commit

Permalink
feat(iotevents): support actions (#18869)
Browse files Browse the repository at this point in the history
This PR allow IoT Events detector model to perform actions as [this documentation](https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-supported-actions.html).
This PR is in roadmap of #17711.

![スクリーンショット 2022-02-08 22 43 33](https://user-images.githubusercontent.com/11013683/152999288-81721f15-fefb-4108-b34b-aab3f88a7ab8.png)

With this fix, all the interfaces of the DetectorModel are now implemented! And next works is implementing expressions and actions.

The exapmle in readme became not simple, so also this PR has sorted explanation of readme.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
yamatatsu committed Mar 4, 2022
1 parent e7c0c75 commit e01654e
Show file tree
Hide file tree
Showing 16 changed files with 608 additions and 26 deletions.
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-iotevents-actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,33 @@
This library contains integration classes to specify actions of state events of Detector Model in `@aws-cdk/aws-iotevents`.
Instances of these classes should be passed to `State` defined in `@aws-cdk/aws-iotevents`
You can define built-in actions to use a timer or set a variable, or send data to other AWS resources.

This library contains integration classes to use a timer or set a variable, or send data to other AWS resources.
AWS IoT Events can trigger actions when it detects a specified event or transition event.

Currently supported are:

- Invoke a Lambda function

## Invoke a Lambda function

The code snippet below creates an Action that invoke a Lambda function
when it is triggered.

```ts
import * as iotevents from '@aws-cdk/aws-iotevents';
import * as actions from '@aws-cdk/aws-iotevents-actions';
import * as lambda from '@aws-cdk/aws-lambda';

declare const input: iotevents.IInput;
declare const func: lambda.IFunction;

const state = new iotevents.State({
stateName: 'MyState',
onEnter: [{
eventName: 'test-event',
condition: iotevents.Expression.currentInput(input),
actions: [new actions.LambdaInvokeAction(func)],
}],
});
```
3 changes: 1 addition & 2 deletions packages/@aws-cdk/aws-iotevents-actions/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// this is placeholder for monocdk
export const dummy = true;
export * from './lambda-invoke-action';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as iotevents from '@aws-cdk/aws-iotevents';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct } from 'constructs';

/**
* The action to write the data to an AWS Lambda function.
*/
export class LambdaInvokeAction implements iotevents.IAction {
/**
* @param func the AWS Lambda function to be invoked by this action
*/
constructor(private readonly func: lambda.IFunction) {
}

bind(_scope: Construct, options: iotevents.ActionBindOptions): iotevents.ActionConfig {
this.func.grantInvoke(options.role);
return {
configuration: {
lambda: {
functionArn: this.func.functionArn,
},
},
};
}
}
12 changes: 10 additions & 2 deletions packages/@aws-cdk/aws-iotevents-actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,19 @@
"jest": "^27.5.1"
},
"dependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-iotevents": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-iotevents": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"Resources": {
"MyInput08947B23": {
"Type": "AWS::IoTEvents::Input",
"Properties": {
"InputDefinition": {
"Attributes": [
{
"JsonPath": "payload.deviceId"
}
]
},
"InputName": "test_input"
}
},
"MyFunctionServiceRole3C357FF2": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
}
},
"MyFunction3BAA72D1": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": "\n exports.handler = (event) => {\n console.log(\"It is test for lambda action of AWS IoT Rule.\", event);\n };"
},
"Role": {
"Fn::GetAtt": [
"MyFunctionServiceRole3C357FF2",
"Arn"
]
},
"Handler": "index.handler",
"Runtime": "nodejs14.x"
},
"DependsOn": [
"MyFunctionServiceRole3C357FF2"
]
},
"MyDetectorModelDetectorModelRoleF2FB4D88": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "iotevents.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"MyDetectorModelDetectorModelRoleDefaultPolicy82887422": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"MyFunction3BAA72D1",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyDetectorModelDetectorModelRoleDefaultPolicy82887422",
"Roles": [
{
"Ref": "MyDetectorModelDetectorModelRoleF2FB4D88"
}
]
}
},
"MyDetectorModel559C0B0E": {
"Type": "AWS::IoTEvents::DetectorModel",
"Properties": {
"DetectorModelDefinition": {
"InitialStateName": "MyState",
"States": [
{
"OnEnter": {
"Events": [
{
"Actions": [
{
"Lambda": {
"FunctionArn": {
"Fn::GetAtt": [
"MyFunction3BAA72D1",
"Arn"
]
}
}
}
],
"Condition": {
"Fn::Join": [
"",
[
"currentInput(\"",
{
"Ref": "MyInput08947B23"
},
"\")"
]
]
},
"EventName": "test-event"
}
]
},
"OnInput": {},
"StateName": "MyState"
}
]
},
"RoleArn": {
"Fn::GetAtt": [
"MyDetectorModelDetectorModelRoleF2FB4D88",
"Arn"
]
},
"Key": "payload.deviceId"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Stack verification steps:
* * put a message
* * aws iotevents-data batch-put-message --messages=messageId=(date | md5),inputName=test_input,payload=(echo '{"payload":{"temperature":31.9,"deviceId":"000"}}' | base64)
* * verify that the lambda logs be put
*/
import * as iotevents from '@aws-cdk/aws-iotevents';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as actions from '../../lib';

class TestStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const input = new iotevents.Input(this, 'MyInput', {
inputName: 'test_input',
attributeJsonPaths: ['payload.deviceId'],
});
const func = new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
code: lambda.Code.fromInline(`
exports.handler = (event) => {
console.log("It is test for lambda action of AWS IoT Rule.", event);
};`,
),
});

const state = new iotevents.State({
stateName: 'MyState',
onEnter: [{
eventName: 'test-event',
condition: iotevents.Expression.currentInput(input),
actions: [new actions.LambdaInvokeAction(func)],
}],
});

new iotevents.DetectorModel(this, 'MyDetectorModel', {
detectorKey: 'payload.deviceId',
initialState: state,
});
}
}

const app = new cdk.App();
new TestStack(app, 'lambda-invoke-action-test-stack');
app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Template } from '@aws-cdk/assertions';
import * as iotevents from '@aws-cdk/aws-iotevents';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as actions from '../../lib';

let stack: cdk.Stack;
let input: iotevents.IInput;
let func: lambda.IFunction;
beforeEach(() => {
stack = new cdk.Stack();
input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input');
func = lambda.Function.fromFunctionAttributes(stack, 'MyFunction', {
functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn',
sameEnvironment: true,
});
});

test('Default property', () => {
// WHEN
new iotevents.DetectorModel(stack, 'MyDetectorModel', {
initialState: new iotevents.State({
stateName: 'test-state',
onEnter: [{
eventName: 'test-eventName',
condition: iotevents.Expression.currentInput(input),
actions: [new actions.LambdaInvokeAction(func)],
}],
}),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', {
DetectorModelDefinition: {
States: [{
OnEnter: {
Events: [{
Actions: [{
Lambda: {
FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn',
},
}],
}],
},
}],
},
RoleArn: {
'Fn::GetAtt': ['MyDetectorModelDetectorModelRoleF2FB4D88', 'Arn'],
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [{
Action: 'lambda:InvokeFunction',
Effect: 'Allow',
Resource: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn',
}],
},
Roles: [{
Ref: 'MyDetectorModelDetectorModelRoleF2FB4D88',
}],
});
});
Loading

0 comments on commit e01654e

Please sign in to comment.