Skip to content

Commit

Permalink
feat(iot): Add IAction
Browse files Browse the repository at this point in the history
This change adds `IAction` to aws-iot for preparing to implemamt AWS IoT Rule
actions.
  • Loading branch information
yamatatsu committed Oct 23, 2021
1 parent 54ca910 commit 7a72b32
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 7 deletions.
36 changes: 32 additions & 4 deletions packages/@aws-cdk/aws-iot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,44 @@ 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 rule that give your devices the ability to interact with AWS services.
You can create a rule with an action that invoke the Lambda action as following:

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

new iot.TopicRule(stack, 'MyTopicRule', {
topicRuleName: 'MyRuleName', // optional property
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
),
actions: [
{
bind: () => ({
configuration: {
lambda: { functionArn: 'test-functionArn' },
},
}),
},
],
});
```

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

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

const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
),
});
topicRule.addAction({
bind: () => ({
configuration: {
lambda: { functionArn: 'test-functionArn' },
},
}),
})
```
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 rule The TopicRule that would trigger this action.
*/
bind(rule: 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
37 changes: 35 additions & 2 deletions packages/@aws-cdk/aws-iot/lib/topic-rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArnFormat, Resource, Stack, IResource } from '@aws-cdk/core';
import { ArnFormat, Resource, Stack, IResource, Lazy } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IAction } from './action';
import { IotSql } from './iot-sql';
import { CfnTopicRule } from './iot.generated';

Expand Down Expand Up @@ -33,6 +34,13 @@ export interface TopicRuleProps {
*/
readonly topicRuleName?: string;

/**
* The actions associated with the rule.
*
* @default No actions will be perform
*/
readonly actions?: Array<IAction>;

/**
* A simplified SQL syntax to filter messages received on an MQTT topic and push the data elsewhere.
*
Expand Down Expand Up @@ -80,6 +88,8 @@ export class TopicRule extends Resource implements ITopicRule {
*/
public readonly topicRuleName: string;

private readonly actions = new Array<CfnTopicRule.ActionProperty>();

constructor(scope: Construct, id: string, props: TopicRuleProps) {
super(scope, id, {
physicalName: props.topicRuleName,
Expand All @@ -90,7 +100,7 @@ export class TopicRule extends Resource implements ITopicRule {
const resource = new CfnTopicRule(this, 'Resource', {
ruleName: this.physicalName,
topicRulePayload: {
actions: [],
actions: Lazy.any({ produce: () => this.actions }),
awsIotSqlVersion: sqlConfig.awsIotSqlVersion,
sql: sqlConfig.sql,
},
Expand All @@ -102,5 +112,28 @@ export class TopicRule extends Resource implements ITopicRule {
resourceName: this.physicalName,
});
this.topicRuleName = this.getResourceNameAttribute(resource.ref);

props.actions?.forEach(action => {
this.addAction(action);
});
}

/**
* Add a action to the rule.
*
* @param action the action to associate with the rule.
*/
public addAction(action: IAction): void {
const { configuration } = action.bind(this);

const keys = Object.keys(configuration);
if (keys.length === 0) {
throw new Error('An action property cannot be an empty object.');
}
if (keys.length >= 2) {
throw new Error(`An action property cannot have multiple keys, received: ${keys}`);
}

this.actions.push(configuration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
"Type": "AWS::IoT::TopicRule",
"Properties": {
"TopicRulePayload": {
"Actions": [],
"Actions": [
{
"Http": {
"Url": "https://example.com"
}
}
],
"AwsIotSqlVersion": "2015-10-08",
"Sql": "SELECT topic(2) as device_id FROM 'device/+/data'"
}
Expand Down
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-iot/test/integ.topic-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ class TestStack extends cdk.Stack {

new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id FROM 'device/+/data'"),
actions: [
{
bind: () => ({
configuration: {
http: { url: 'https://example.com' },
},
}),
},
],
});
}
}
Expand Down
113 changes: 113 additions & 0 deletions packages/@aws-cdk/aws-iot/test/topic-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,119 @@ test.each([
}).toThrow('IoT SQL string cannot be empty');
});

test('can set actions', () => {
const stack = new cdk.Stack();

const action1: iot.IAction = {
bind: () => ({
configuration: {
http: { url: 'http://example.com' },
},
}),
};
const action2: iot.IAction = {
bind: () => ({
configuration: {
lambda: { functionArn: 'test-functionArn' },
},
}),
};

new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"),
actions: [action1, action2],
});

Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: { Url: 'http://example.com' },
},
{
Lambda: { FunctionArn: 'test-functionArn' },
},
],
Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
},
});
});

test('can add an action', () => {
const stack = new cdk.Stack();

const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"),
});
topicRule.addAction({
bind: () => ({
configuration: {
http: { url: 'http://example.com' },
},
}),
});
topicRule.addAction({
bind: () => ({
configuration: {
lambda: { functionArn: 'test-functionArn' },
},
}),
});

Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: { Url: 'http://example.com' },
},
{
Lambda: { FunctionArn: 'test-functionArn' },
},
],
Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
},
});
});

test('cannot add an action as empty object', () => {
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"),
});

const emptyKeysAction: iot.IAction = {
bind: () => ({
configuration: {},
}),
};

expect(() => {
topicRule.addAction(emptyKeysAction);
}).toThrow('An action property cannot be an empty object.');
});

test('cannot add an action that have multiple keys', () => {
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"),
});

const multipleKeysAction: iot.IAction = {
bind: () => ({
configuration: {
http: { url: 'http://example.com' },
lambda: { functionArn: 'test-functionArn' },
},
}),
};

expect(() => {
topicRule.addAction(multipleKeysAction);
}).toThrow(
'An action property cannot have multiple keys, received: http,lambda',
);
});

test('can import a TopicRule by ARN', () => {
const stack = new cdk.Stack();

Expand Down

0 comments on commit 7a72b32

Please sign in to comment.