Skip to content

Commit

Permalink
feat(iot): add the TopicRule L2 construct (#16681)
Browse files Browse the repository at this point in the history
I tried to implement aws-iot L2 Constructs.

I did following:

1. add L2 construct
1. add unit tests
1. add integration test
1. test with deploying to my AWS account and sending MQTT.
1. update package.json

resolves: #16602

I should do following for undrafting:

- [x] write comments
- [x] implement other actions
  Following is not implemented yet, but I will implements other PR.
  - Elasticsearch
  - Kinesis Firehose
  - Kinesis Stream
  - http
  - IoT Analytics
  - IoT Events
  - IoT SiteWise
  - kafka
  - Step Functions
- [x] write README

----

## Design

### TopicRule and IAction

![image](https://user-images.githubusercontent.com/11013683/136200920-9aa1aa58-2e9f-4a0d-a161-bbe251d02f7d.png)

### Implements of IAction

![image](https://user-images.githubusercontent.com/11013683/136201053-4f693683-3318-4fbf-9a7e-cd3f8ac1a93e.png)



----

*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 authored Oct 21, 2021
1 parent ee24046 commit 86f85ce
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 9 deletions.
41 changes: 40 additions & 1 deletion packages/@aws-cdk/aws-iot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,47 @@
>
> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib
![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)

> The APIs of higher level constructs in this module are experimental and under active development.
> They are subject to non-backward compatible changes or removal in any future version. These are
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
> announced in the release notes. This means that while you may use them, you may need to update
> your source code when upgrading to a newer version of this package.
---

<!--END STABILITY BANNER-->

This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.
AWS IoT Core lets you connect billions of IoT devices and route trillions of
messages to AWS services without managing infrastructure.

## Installation

Install the module:

```console
$ npm i @aws-cdk/aws-iot
```

Import it into your code:

```ts
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:

```ts
new iot.TopicRule(stack, 'MyTopicRule', {
topicRuleName: 'MyRuleName', // optional property
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id, temperature FROM 'device/+/data'",
),
});
```
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-iot/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from './iot-sql';
export * from './topic-rule';

// AWS::IoT CloudFormation Resources:
export * from './iot.generated';
75 changes: 75 additions & 0 deletions packages/@aws-cdk/aws-iot/lib/iot-sql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Construct } from 'constructs';

/**
* The type returned from the `bind()` method in {@link IotSql}.
*/
export interface IotSqlConfig {
/**
* The version of the SQL rules engine to use when evaluating the rule.
*/
readonly awsIotSqlVersion: string;

/**
* The SQL statement used to query the topic.
*/
readonly sql: string;
}

/**
* Defines AWS IoT SQL.
*/
export abstract class IotSql {
/**
* Uses the original SQL version built on 2015-10-08.
*
* @param sql The actual SQL-like syntax query
* @returns Instance of IotSql
*/
public static fromStringAsVer20151008(sql: string): IotSql {
return new IotSqlImpl('2015-10-08', sql);
}

/**
* Uses the SQL version built on 2016-03-23.
*
* @param sql The actual SQL-like syntax query
* @returns Instance of IotSql
*/
public static fromStringAsVer20160323(sql: string): IotSql {
return new IotSqlImpl('2016-03-23', sql);
}

/**
* Uses the most recent beta SQL version. If you use this version, it might
* introduce breaking changes to your rules.
*
* @param sql The actual SQL-like syntax query
* @returns Instance of IotSql
*/
public static fromStringAsVerNewestUnstable(sql: string): IotSql {
return new IotSqlImpl('beta', sql);
}

/**
* Returns the IoT SQL configuration.
*/
public abstract bind(scope: Construct): IotSqlConfig;
}


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

if (sql === '') {
throw new Error('IoT SQL string cannot be empty');
}
}

bind(_scope: Construct): IotSqlConfig {
return {
awsIotSqlVersion: this.version,
sql: this.sql,
};
}
}
106 changes: 106 additions & 0 deletions packages/@aws-cdk/aws-iot/lib/topic-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ArnFormat, Resource, Stack, IResource } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IotSql } from './iot-sql';
import { CfnTopicRule } from './iot.generated';

/**
* Represents an AWS IoT Rule
*/
export interface ITopicRule extends IResource {
/**
* The value of the topic rule Amazon Resource Name (ARN), such as
* arn:aws:iot:us-east-2:123456789012:rule/rule_name
*
* @attribute
*/
readonly topicRuleArn: string;

/**
* The name topic rule
*
* @attribute
*/
readonly topicRuleName: string;
}

/**
* Properties for defining an AWS IoT Rule
*/
export interface TopicRuleProps {
/**
* The name of the rule.
* @default None
*/
readonly topicRuleName?: string;

/**
* A simplified SQL syntax to filter messages received on an MQTT topic and push the data elsewhere.
*
* @see https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html
*/
readonly sql: IotSql;
}

/**
* Defines an AWS IoT Rule in this stack.
*/
export class TopicRule extends Resource implements ITopicRule {
/**
* Import an existing AWS IoT Rule provided an ARN
*
* @param scope The parent creating construct (usually `this`).
* @param id The construct's name.
* @param topicRuleArn AWS IoT Rule ARN (i.e. arn:aws:iot:<region>:<account-id>:rule/MyRule).
*/
public static fromTopicRuleArn(scope: Construct, id: string, topicRuleArn: string): ITopicRule {
const parts = Stack.of(scope).splitArn(topicRuleArn, ArnFormat.SLASH_RESOURCE_NAME);
if (!parts.resourceName) {
throw new Error(`Missing topic rule name in ARN: '${topicRuleArn}'`);
}
const resourceName = parts.resourceName;

class Import extends Resource implements ITopicRule {
public readonly topicRuleArn = topicRuleArn;
public readonly topicRuleName = resourceName;
}
return new Import(scope, id, {
environmentFromArn: topicRuleArn,
});
}

/**
* Arn of this rule
* @attribute
*/
public readonly topicRuleArn: string;

/**
* Name of this rule
* @attribute
*/
public readonly topicRuleName: string;

constructor(scope: Construct, id: string, props: TopicRuleProps) {
super(scope, id, {
physicalName: props.topicRuleName,
});

const sqlConfig = props.sql.bind(this);

const resource = new CfnTopicRule(this, 'Resource', {
ruleName: this.physicalName,
topicRulePayload: {
actions: [],
awsIotSqlVersion: sqlConfig.awsIotSqlVersion,
sql: sqlConfig.sql,
},
});

this.topicRuleArn = this.getResourceArnAttribute(resource.attrArn, {
service: 'iot',
resource: 'rule',
resourceName: this.physicalName,
});
this.topicRuleName = this.getResourceNameAttribute(resource.ref);
}
}
6 changes: 4 additions & 2 deletions packages/@aws-cdk/aws-iot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@
"devDependencies": {
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^26.0.24"
"@types/jest": "^26.0.24",
"jest": "^26.6.3"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
Expand All @@ -91,7 +93,7 @@
"node": ">= 10.13.0 <13 || >=13.7.0"
},
"stability": "experimental",
"maturity": "cfn-only",
"maturity": "experimental",
"awscdkio": {
"announce": false
},
Expand Down
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-iot/test/integ.topic-rule.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Resources": {
"TopicRule40A4EA44": {
"Type": "AWS::IoT::TopicRule",
"Properties": {
"TopicRulePayload": {
"Actions": [],
"AwsIotSqlVersion": "2015-10-08",
"Sql": "SELECT topic(2) as device_id FROM 'device/+/data'"
}
}
}
}
}
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-iot/test/integ.topic-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// !cdk-integ pragma:ignore-assets
import * as cdk from '@aws-cdk/core';
import * as iot 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);

new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id FROM 'device/+/data'"),
});
}
}

new TestStack(app, 'test-stack');
app.synth();
6 changes: 0 additions & 6 deletions packages/@aws-cdk/aws-iot/test/iot.test.ts

This file was deleted.

Loading

0 comments on commit 86f85ce

Please sign in to comment.