Skip to content

Commit 35fc169

Browse files
authoredAug 8, 2022
feat(iot-actions): support for sending messages to iot-events (#19953)
This PR includes to support the action for sending messages to IoT Events. This feature is described [this documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iotevents-rule-action.html). I actually confirmed that the behavior of the action deployed by integ-test is all right. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 30083fc commit 35fc169

29 files changed

+1028
-28
lines changed
 

Diff for: ‎packages/@aws-cdk/aws-iot-actions/README.md

+33-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Currently supported are:
3232
- Send messages to SQS queues
3333
- Publish messages on SNS topics
3434
- Write messages into columns of DynamoDB
35+
- Put messages IoT Events input
3536

3637
## Republish a message to another MQTT topic
3738

@@ -73,7 +74,7 @@ new iot.TopicRule(this, 'TopicRule', {
7374

7475
## Put objects to a S3 bucket
7576

76-
The code snippet below creates an AWS IoT Rule that put objects to a S3 bucket
77+
The code snippet below creates an AWS IoT Rule that puts objects to a S3 bucket
7778
when it is triggered.
7879

7980
```ts
@@ -126,7 +127,7 @@ new iot.TopicRule(this, 'TopicRule', {
126127

127128
## Put logs to CloudWatch Logs
128129

129-
The code snippet below creates an AWS IoT Rule that put logs to CloudWatch Logs
130+
The code snippet below creates an AWS IoT Rule that puts logs to CloudWatch Logs
130131
when it is triggered.
131132

132133
```ts
@@ -194,7 +195,7 @@ const topicRule = new iot.TopicRule(this, 'TopicRule', {
194195

195196
## Put records to Kinesis Data stream
196197

197-
The code snippet below creates an AWS IoT Rule that put records to Kinesis Data
198+
The code snippet below creates an AWS IoT Rule that puts records to Kinesis Data
198199
stream when it is triggered.
199200

200201
```ts
@@ -214,7 +215,7 @@ const topicRule = new iot.TopicRule(this, 'TopicRule', {
214215

215216
## Put records to Kinesis Data Firehose stream
216217

217-
The code snippet below creates an AWS IoT Rule that put records to Put records
218+
The code snippet below creates an AWS IoT Rule that puts records to Put records
218219
to Kinesis Data Firehose stream when it is triggered.
219220

220221
```ts
@@ -299,3 +300,31 @@ const topicRule = new iot.TopicRule(this, 'TopicRule', {
299300
],
300301
});
301302
```
303+
304+
## Put messages IoT Events input
305+
306+
The code snippet below creates an AWS IoT Rule that puts messages
307+
to an IoT Events input when it is triggered:
308+
309+
```ts
310+
import * as iotevents from '@aws-cdk/aws-iotevents';
311+
import * as iam from '@aws-cdk/aws-iam';
312+
313+
declare const role: iam.IRole;
314+
315+
const input = new iotevents.Input(this, 'MyInput', {
316+
attributeJsonPaths: ['payload.temperature', 'payload.transactionId'],
317+
});
318+
const topicRule = new iot.TopicRule(this, 'TopicRule', {
319+
sql: iot.IotSql.fromStringAsVer20160323(
320+
"SELECT * FROM 'device/+/data'",
321+
),
322+
actions: [
323+
new actions.IotEventsPutMessageAction(input, {
324+
batchMode: true, // optional property, default is 'false'
325+
messageId: '${payload.transactionId}', // optional property, default is a new UUID
326+
role: role, // optional property, default is a new UUID
327+
}),
328+
],
329+
});
330+
```

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export class CloudWatchLogsAction implements iot.IAction {
2727
this.role = props.role;
2828
}
2929

30-
bind(rule: iot.ITopicRule): iot.ActionConfig {
30+
/**
31+
* @internal
32+
*/
33+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
3134
const role = this.role ?? singletonActionRole(rule);
3235
this.logGroup.grantWrite(role);
3336
this.logGroup.grant(role, 'logs:DescribeLogStreams');

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-put-metric-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ export class CloudWatchPutMetricAction implements iot.IAction {
5757
constructor(private readonly props: CloudWatchPutMetricActionProps) {
5858
}
5959

60-
bind(rule: iot.ITopicRule): iot.ActionConfig {
60+
/**
61+
* @internal
62+
*/
63+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
6164
const role = this.props.role ?? singletonActionRole(rule);
6265
cloudwatch.Metric.grantPutMetricData(role);
6366

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-set-alarm-state-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ export class CloudWatchSetAlarmStateAction implements iot.IAction {
3131
) {
3232
}
3333

34-
bind(topicRule: iot.ITopicRule): iot.ActionConfig {
34+
/**
35+
* @internal
36+
*/
37+
public _bind(topicRule: iot.ITopicRule): iot.ActionConfig {
3538
const role = this.props.role ?? singletonActionRole(topicRule);
3639
role.addToPrincipalPolicy(new iam.PolicyStatement({
3740
actions: ['cloudwatch:SetAlarmState'],

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/dynamodbv2-put-item-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export class DynamoDBv2PutItemAction implements iot.IAction {
2424
this.role = props.role;
2525
}
2626

27-
bind(rule: iot.ITopicRule): iot.ActionConfig {
27+
/**
28+
* @internal
29+
*/
30+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
2831
const role = this.role ?? singletonActionRole(rule);
2932
role.addToPrincipalPolicy(new iam.PolicyStatement({
3033
actions: ['dynamodb:PutItem'],

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/firehose-put-record-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export class FirehosePutRecordAction implements iot.IAction {
7070
this.role = props.role;
7171
}
7272

73-
bind(rule: iot.ITopicRule): iot.ActionConfig {
73+
/**
74+
* @internal
75+
*/
76+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
7477
const role = this.role ?? singletonActionRole(rule);
7578
this.stream.grantPutRecords(role);
7679

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './cloudwatch-set-alarm-state-action';
44
export * from './common-action-props';
55
export * from './dynamodbv2-put-item-action';
66
export * from './firehose-put-record-action';
7+
export * from './iotevents-put-message-action';
78
export * from './iot-republish-action';
89
export * from './kinesis-put-record-action';
910
export * from './lambda-function-action';

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/iot-republish-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ export class IotRepublishMqttAction implements iot.IAction {
5252
this.role = props.role;
5353
}
5454

55-
bind(rule: iot.ITopicRule): iot.ActionConfig {
55+
/**
56+
* @internal
57+
*/
58+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
5659
const role = this.role ?? singletonActionRole(rule);
5760
role.addToPrincipalPolicy(new iam.PolicyStatement({
5861
actions: ['iot:Publish'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as iot from '@aws-cdk/aws-iot';
3+
import * as iotevents from '@aws-cdk/aws-iotevents';
4+
import { CommonActionProps } from './common-action-props';
5+
import { singletonActionRole } from './private/role';
6+
7+
/**
8+
* Configuration properties of an action for the IoT Events.
9+
*/
10+
export interface IotEventsPutMessageActionProps extends CommonActionProps {
11+
/**
12+
* Whether to process the event actions as a batch.
13+
*
14+
* When batchMode is true, you can't specify a messageId.
15+
*
16+
* When batchMode is true and the rule SQL statement evaluates to an Array,
17+
* each Array element is treated as a separate message when Events by calling BatchPutMessage.
18+
* The resulting array can't have more than 10 messages.
19+
*
20+
* @default false
21+
*/
22+
readonly batchMode?: boolean;
23+
24+
/**
25+
* The ID of the message.
26+
*
27+
* When batchMode is true, you can't specify a messageId--a new UUID value will be assigned.
28+
* Assign a value to this property to ensure that only one input (message) with a given messageId will be processed by an AWS IoT Events detector.
29+
*
30+
* @default - none -- a new UUID value will be assigned
31+
*/
32+
readonly messageId?: string;
33+
}
34+
35+
/**
36+
* The action to put the message from an MQTT message to the IoT Events input.
37+
*/
38+
export class IotEventsPutMessageAction implements iot.IAction {
39+
private readonly batchMode?: boolean;
40+
private readonly messageId?: string;
41+
private readonly role?: iam.IRole;
42+
43+
/**
44+
* @param input The IoT Events input to put messages.
45+
* @param props Optional properties to not use default
46+
*/
47+
constructor(private readonly input: iotevents.IInput, props: IotEventsPutMessageActionProps = {}) {
48+
this.batchMode = props.batchMode;
49+
this.messageId = props.messageId;
50+
this.role = props.role;
51+
52+
if (this.batchMode && this.messageId) {
53+
throw new Error('messageId is not allowed when batchMode is true');
54+
}
55+
}
56+
57+
/**
58+
* @internal
59+
*/
60+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
61+
const role = this.role ?? singletonActionRole(rule);
62+
this.input.grantWrite(role);
63+
64+
return {
65+
configuration: {
66+
iotEvents: {
67+
batchMode: this.batchMode,
68+
inputName: this.input.inputName,
69+
messageId: this.messageId,
70+
roleArn: role.roleArn,
71+
},
72+
},
73+
};
74+
}
75+
}

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/kinesis-put-record-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export class KinesisPutRecordAction implements iot.IAction {
3838
this.role = props.role;
3939
}
4040

41-
bind(rule: iot.ITopicRule): iot.ActionConfig {
41+
/**
42+
* @internal
43+
*/
44+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
4245
const role = this.role ?? singletonActionRole(rule);
4346
role.addToPrincipalPolicy(new iam.PolicyStatement({
4447
actions: ['kinesis:PutRecord'],

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export class LambdaFunctionAction implements iot.IAction {
1212
*/
1313
constructor(private readonly func: lambda.IFunction) {}
1414

15-
bind(topicRule: iot.ITopicRule): iot.ActionConfig {
15+
/**
16+
* @internal
17+
*/
18+
public _bind(topicRule: iot.ITopicRule): iot.ActionConfig {
1619
this.func.addPermission(`${Names.nodeUniqueId(topicRule.node)}:IotLambdaFunctionAction`, {
1720
action: 'lambda:InvokeFunction',
1821
principal: new iam.ServicePrincipal('iot.amazonaws.com'),

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export class S3PutObjectAction implements iot.IAction {
4646
this.role = props.role;
4747
}
4848

49-
bind(rule: iot.ITopicRule): iot.ActionConfig {
49+
/**
50+
* @internal
51+
*/
52+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
5053
const role = this.role ?? singletonActionRole(rule);
5154
role.addToPrincipalPolicy(new iam.PolicyStatement({
5255
actions: ['s3:PutObject'],

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/sns-topic-action.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ export class SnsTopicAction implements iot.IAction {
5858
this.messageFormat = props.messageFormat;
5959
}
6060

61-
bind(rule: iot.ITopicRule): iot.ActionConfig {
61+
/**
62+
* @internal
63+
*/
64+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
6265
const role = this.role ?? singletonActionRole(rule);
6366
this.topic.grantPublish(role);
6467

@@ -72,4 +75,4 @@ export class SnsTopicAction implements iot.IAction {
7275
},
7376
};
7477
}
75-
}
78+
}

Diff for: ‎packages/@aws-cdk/aws-iot-actions/lib/sqs-queue-action.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ export class SqsQueueAction implements iot.IAction {
3434
this.useBase64 = props.useBase64;
3535
}
3636

37-
bind(rule: iot.ITopicRule): iot.ActionConfig {
37+
/**
38+
* @internal
39+
*/
40+
public _bind(rule: iot.ITopicRule): iot.ActionConfig {
3841
const role = this.role ?? singletonActionRole(rule);
3942
role.addToPrincipalPolicy(new iam.PolicyStatement({
4043
actions: ['sqs:SendMessage'],

Diff for: ‎packages/@aws-cdk/aws-iot-actions/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"@aws-cdk/aws-dynamodb": "0.0.0",
9393
"@aws-cdk/aws-iam": "0.0.0",
9494
"@aws-cdk/aws-iot": "0.0.0",
95+
"@aws-cdk/aws-iotevents": "0.0.0",
9596
"@aws-cdk/aws-kinesis": "0.0.0",
9697
"@aws-cdk/aws-kinesisfirehose": "0.0.0",
9798
"@aws-cdk/aws-lambda": "0.0.0",
@@ -109,6 +110,7 @@
109110
"@aws-cdk/aws-dynamodb": "0.0.0",
110111
"@aws-cdk/aws-iam": "0.0.0",
111112
"@aws-cdk/aws-iot": "0.0.0",
113+
"@aws-cdk/aws-iotevents": "0.0.0",
112114
"@aws-cdk/aws-kinesis": "0.0.0",
113115
"@aws-cdk/aws-kinesisfirehose": "0.0.0",
114116
"@aws-cdk/aws-lambda": "0.0.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as iot from '@aws-cdk/aws-iot';
2+
import * as iotevents from '@aws-cdk/aws-iotevents';
3+
import * as logs from '@aws-cdk/aws-logs';
4+
import * as cdk from '@aws-cdk/core';
5+
import { IntegTest } from '@aws-cdk/integ-tests';
6+
import * as actions from '../../lib';
7+
8+
class TestStack extends cdk.Stack {
9+
public readonly detectorModelName: string;
10+
11+
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
12+
super(scope, id, props);
13+
14+
const logGroup = new logs.LogGroup(this, 'logs', { removalPolicy: cdk.RemovalPolicy.DESTROY });
15+
const topicRule = new iot.TopicRule(this, 'TopicRule', {
16+
sql: iot.IotSql.fromStringAsVer20160323(
17+
"SELECT * FROM 'device/+/data'",
18+
),
19+
errorAction: new actions.CloudWatchLogsAction(logGroup),
20+
});
21+
22+
const input = new iotevents.Input(this, 'MyInput', {
23+
attributeJsonPaths: ['payload.deviceId'],
24+
});
25+
26+
const detectorModel = new iotevents.DetectorModel(this, 'MyDetectorModel', {
27+
detectorKey: 'payload.deviceId',
28+
initialState: new iotevents.State({
29+
stateName: 'initialState',
30+
onEnter: [{
31+
eventName: 'enter',
32+
condition: iotevents.Expression.currentInput(input),
33+
}],
34+
}),
35+
});
36+
37+
topicRule.addAction(
38+
new actions.IotEventsPutMessageAction(input, {
39+
batchMode: true,
40+
}),
41+
);
42+
43+
this.detectorModelName = detectorModel.detectorModelName;
44+
}
45+
}
46+
47+
// App
48+
const app = new cdk.App();
49+
const stack = new TestStack(app, 'iotevents-put-message-action-test-stack');
50+
new IntegTest(app, 'iotevents', { testCases: [stack] });
51+
52+
app.synth();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":"20.0.0"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"version": "20.0.0",
3+
"testCases": {
4+
"iotevents/DefaultTest": {
5+
"stacks": [
6+
"iotevents-put-message-action-test-stack"
7+
],
8+
"assertionStack": "ioteventsDefaultTestDeployAssertE216288D"
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)
Please sign in to comment.