Skip to content

Commit

Permalink
feat(iot): allow setting Actions of TopicRule (#17110)
Browse files Browse the repository at this point in the history
I'm trying to implement aws-iot L2 Constructs.

This PR is the next step of #16681

refar: 
- #16681 (comment)

----

*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 Oct 28, 2021
1 parent 7f19400 commit 0cabb9f
Show file tree
Hide file tree
Showing 16 changed files with 472 additions and 23 deletions.
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-iot-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 send data to any number of
supported AWS Services. Instances of these classes should be passed to
`TopicRule` defined in `@aws-cdk/aws-iot`.

Currently supported are:

- Invoke a Lambda function

## Invoke a Lambda function

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

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

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);
};`
),
});

new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'"),
actions: [new actions.LambdaFunctionAction(func)],
});
```
3 changes: 1 addition & 2 deletions packages/@aws-cdk/aws-iot-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-function-action';
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as iam from '@aws-cdk/aws-iam';
import * as iot from '@aws-cdk/aws-iot';
import * as lambda from '@aws-cdk/aws-lambda';

/**
* The action to invoke an AWS Lambda function, passing in an MQTT message.
*/
export class LambdaFunctionAction implements iot.IAction {
/**
* @param func The lambda function to be invoked by this action
*/
constructor(private readonly func: lambda.IFunction) {}

bind(topicRule: iot.ITopicRule): iot.ActionConfig {
this.func.addPermission('invokedByAwsIotRule', {
action: 'lambda:InvokeFunction',
principal: new iam.ServicePrincipal('iot.amazonaws.com'),
sourceAccount: topicRule.env.account,
sourceArn: topicRule.topicRuleArn,
});

return {
configuration: {
lambda: {
functionArn: this.func.functionArn,
},
},
};
}
}
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-iot-actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,19 @@
"jest": "^26.6.3"
},
"dependencies": {
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-iot": "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/aws-iam": "0.0.0",
"@aws-cdk/aws-iot": "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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"Resources": {
"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"
]
},
"MyFunctioninvokedByAwsIotRule5581F304": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"MyFunction3BAA72D1",
"Arn"
]
},
"Principal": "iot.amazonaws.com",
"SourceAccount": {
"Ref": "AWS::AccountId"
},
"SourceArn": {
"Fn::GetAtt": [
"TopicRule40A4EA44",
"Arn"
]
}
}
},
"TopicRule40A4EA44": {
"Type": "AWS::IoT::TopicRule",
"Properties": {
"TopicRulePayload": {
"Actions": [
{
"Lambda": {
"FunctionArn": {
"Fn::GetAtt": [
"MyFunction3BAA72D1",
"Arn"
]
}
}
}
],
"AwsIotSqlVersion": "2016-03-23",
"Sql": "SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// !cdk-integ pragma:ignore-assets
import * as iot from '@aws-cdk/aws-iot';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as actions from '../../lib';

const app = new cdk.App();

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

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);
};"`,
),
});

new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'"),
actions: [new actions.LambdaFunctionAction(func)],
});
}
}

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

test('create a topic rule with lambda action and a lambda permission to be invoked by the topic rule', () => {
// GIVEN
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"),
});
const func = new lambda.Function(stack, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
code: lambda.Code.fromInline('console.log("foo")'),
});

// WHEN
topicRule.addAction(new actions.LambdaFunctionAction(func));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Lambda: {
FunctionArn: {
'Fn::GetAtt': [
'MyFunction3BAA72D1',
'Arn',
],
},
},
},
],
},
});

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', {
Action: 'lambda:InvokeFunction',
FunctionName: {
'Fn::GetAtt': [
'MyFunction3BAA72D1',
'Arn',
],
},
Principal: 'iot.amazonaws.com',
SourceAccount: { Ref: 'AWS::AccountId' },
SourceArn: {
'Fn::GetAtt': [
'MyTopicRule4EC2091C',
'Arn',
],
},
});
});
35 changes: 27 additions & 8 deletions packages/@aws-cdk/aws-iot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,35 @@ import * as iot from '@aws-cdk/aws-iot';

## `TopicRule`

The `TopicRule` construct defined Rules that give your devices the ability to
interact with AWS services.

For example, to define a rule:
Create a topic rule that give your devices the ability to interact with AWS services.
You can create a topic rule with an action that invoke the Lambda action as following:

```ts
new iot.TopicRule(stack, 'MyTopicRule', {
topicRuleName: 'MyRuleName', // optional property
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
import * as iot from '@aws-cdk/aws-iot';
import * as actions from '@aws-cdk/aws-iot-actions';
import * as lambda from '@aws-cdk/aws-lambda';

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);
};`
),
});

new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"),
actions: [new actions.LambdaFunctionAction(func)],
});
```

Or, you can add an action after constructing the `TopicRule` instance as following:

```ts
const topicRule = new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"),
});
topicRule.addAction(new actions.LambdaFunctionAction(func))
```
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-iot/lib/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CfnTopicRule } from './iot.generated';
import { ITopicRule } from './topic-rule';

/**
* An abstract action for TopicRule.
*/
export interface IAction {
/**
* Returns the topic rule action specification.
*
* @param topicRule The TopicRule that would trigger this action.
*/
bind(topicRule: ITopicRule): ActionConfig;
}

/**
* Properties for an topic rule action
*/
export interface ActionConfig {
/**
* The configuration for this action.
*/
readonly configuration: CfnTopicRule.ActionProperty;
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-iot/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './action';
export * from './iot-sql';
export * from './topic-rule';

Expand Down
1 change: 0 additions & 1 deletion packages/@aws-cdk/aws-iot/lib/iot-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export abstract class IotSql {
public abstract bind(scope: Construct): IotSqlConfig;
}


class IotSqlImpl extends IotSql {
constructor(private readonly version: string, private readonly sql: string) {
super();
Expand Down
Loading

0 comments on commit 0cabb9f

Please sign in to comment.