From 9f03dc4c5b75225942037fb6c8fa7d6abf35fe11 Mon Sep 17 00:00:00 2001 From: Tatsuya Yamamoto Date: Sat, 11 Dec 2021 09:14:37 +0900 Subject: [PATCH] feat(iotevents): add IoT Events input L2 Construct (#17847) This is proposed by https://github.com/aws/aws-cdk/issues/17711. This PR was created for implemeting `Input` L2 Construct. Implementing it is needed before `DetectorModel`. The reason is described in here: https://github.com/aws/aws-cdk/issues/17711#issuecomment-986153267 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-iotevents/README.md | 39 ++++++++-- packages/@aws-cdk/aws-iotevents/lib/index.ts | 2 + packages/@aws-cdk/aws-iotevents/lib/input.ts | 71 +++++++++++++++++ packages/@aws-cdk/aws-iotevents/package.json | 6 +- .../@aws-cdk/aws-iotevents/test/input.test.ts | 78 +++++++++++++++++++ .../test/integ.detector-model.expected.json | 17 ++++ .../test/integ.detector-model.ts | 18 +++++ .../aws-iotevents/test/iotevents.test.ts | 6 -- 8 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-iotevents/lib/input.ts create mode 100644 packages/@aws-cdk/aws-iotevents/test/input.test.ts create mode 100644 packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json create mode 100644 packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts delete mode 100644 packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index e9a17af332776..6dc6a681636cc 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -1,4 +1,5 @@ # AWS::IoTEvents Construct Library + --- @@ -9,23 +10,45 @@ > > [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. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +AWS IoT Events enables you to monitor your equipment or device fleets for +failures or changes in operation, and to trigger actions when such events +occur. + +## Installation + +Install the module: + +```console +$ npm i @aws-cdk/aws-iotevents +``` + +Import it into your code: ```ts nofixture import * as iotevents from '@aws-cdk/aws-iotevents'; ``` - - -There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. -However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly. +## `Input` -For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::IoTEvents](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_IoTEvents.html). +Add an AWS IoT Events input to your stack: -(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.) +```ts +import * as iotevents from '@aws-cdk/aws-iotevents'; - +new iotevents.Input(this, 'MyInput', { + inputName: 'my_input', + attributeJsonPaths: ['payload.temperature'], +}); +``` diff --git a/packages/@aws-cdk/aws-iotevents/lib/index.ts b/packages/@aws-cdk/aws-iotevents/lib/index.ts index 6390f9ed0b0ee..3851e30984391 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/index.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/index.ts @@ -1,2 +1,4 @@ +export * from './input'; + // AWS::IoTEvents CloudFormation Resources: export * from './iotevents.generated'; diff --git a/packages/@aws-cdk/aws-iotevents/lib/input.ts b/packages/@aws-cdk/aws-iotevents/lib/input.ts new file mode 100644 index 0000000000000..e4bba5684b7a4 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/lib/input.ts @@ -0,0 +1,71 @@ +import { Resource, IResource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnInput } from './iotevents.generated'; + +/** + * Represents an AWS IoT Events input + */ +export interface IInput extends IResource { + /** + * The name of the input + * @attribute + */ + readonly inputName: string; +} + +/** + * Properties for defining an AWS IoT Events input + */ +export interface InputProps { + /** + * The name of the input + * + * @default - CloudFormation will generate a unique name of the input + */ + readonly inputName?: string, + + /** + * An expression that specifies an attribute-value pair in a JSON structure. + * Use this to specify an attribute from the JSON payload that is made available + * by the input. Inputs are derived from messages sent to AWS IoT Events (BatchPutMessage). + * Each such message contains a JSON payload. The attribute (and its paired value) + * specified here are available for use in the condition expressions used by detectors. + */ + readonly attributeJsonPaths: string[]; +} + +/** + * Defines an AWS IoT Events input in this stack. + */ +export class Input extends Resource implements IInput { + /** + * Import an existing input + */ + public static fromInputName(scope: Construct, id: string, inputName: string): IInput { + class Import extends Resource implements IInput { + public readonly inputName = inputName; + } + return new Import(scope, id); + } + + public readonly inputName: string; + + constructor(scope: Construct, id: string, props: InputProps) { + super(scope, id, { + physicalName: props.inputName, + }); + + if (props.attributeJsonPaths.length === 0) { + throw new Error('attributeJsonPaths property cannot be empty'); + } + + const resource = new CfnInput(this, 'Resource', { + inputName: this.physicalName, + inputDefinition: { + attributes: props.attributeJsonPaths.map(path => ({ jsonPath: path })), + }, + }); + + this.inputName = this.getResourceNameAttribute(resource.ref); + } +} diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 7c69ce3fe3da5..50ed464cf76d7 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -76,9 +76,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": "^27.0.3" + "@types/jest": "^27.0.3", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/core": "0.0.0", @@ -92,7 +94,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-iotevents/test/input.test.ts b/packages/@aws-cdk/aws-iotevents/test/input.test.ts new file mode 100644 index 0000000000000..11b457bb0cf1b --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/input.test.ts @@ -0,0 +1,78 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import * as iotevents from '../lib'; + +test('Default property', () => { + const stack = new cdk.Stack(); + + // WHEN + new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: ['payload.temperature'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::Input', { + InputDefinition: { + Attributes: [{ JsonPath: 'payload.temperature' }], + }, + }); +}); + +test('can get input name', () => { + const stack = new cdk.Stack(); + // GIVEN + const input = new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: ['payload.temperature'], + }); + + // WHEN + new cdk.CfnResource(stack, 'Res', { + type: 'Test::Resource', + properties: { + InputName: input.inputName, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Test::Resource', { + InputName: { Ref: 'MyInput08947B23' }, + }); +}); + +test('can set physical name', () => { + const stack = new cdk.Stack(); + + // WHEN + new iotevents.Input(stack, 'MyInput', { + inputName: 'test_input', + attributeJsonPaths: ['payload.temperature'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::Input', { + InputName: 'test_input', + }); +}); + +test('can import a Input by inputName', () => { + const stack = new cdk.Stack(); + + // WHEN + const inputName = 'test-input-name'; + const topicRule = iotevents.Input.fromInputName(stack, 'InputFromInputName', inputName); + + // THEN + expect(topicRule).toMatchObject({ + inputName, + }); +}); + +test('cannot be created with an empty array of attributeJsonPaths', () => { + const stack = new cdk.Stack(); + + expect(() => { + new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: [], + }); + }).toThrow('attributeJsonPaths property cannot be empty'); +}); diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json new file mode 100644 index 0000000000000..1f5d452b5475d --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json @@ -0,0 +1,17 @@ +{ + "Resources": { + "MyInput08947B23": { + "Type": "AWS::IoTEvents::Input", + "Properties": { + "InputDefinition": { + "Attributes": [ + { + "JsonPath": "payload.temperature" + } + ] + }, + "InputName": "test_input" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts new file mode 100644 index 0000000000000..cb900c83a3f44 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -0,0 +1,18 @@ +import * as cdk from '@aws-cdk/core'; +import * as iotevents 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 iotevents.Input(this, 'MyInput', { + inputName: 'test_input', + attributeJsonPaths: ['payload.temperature'], + }); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts b/packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts deleted file mode 100644 index 465c7bdea0693..0000000000000 --- a/packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assertions'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -});