Skip to content

Commit d0960f1

Browse files
authored
feat(iotevents): add DetectorModel L2 Construct (#18049)
This PR is proposed by #17711. The first step of the roadmap in #17711 is implemented in this PR. > 1. implement DetectorModel and State with only required properties > - It will not be able to have multiple states yet. If this PR is merged, the simplest detector model can be created as following: ![image](https://user-images.githubusercontent.com/11013683/146365658-248bba67-743c-4ba3-a195-56223146525f.png) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 3d192a9 commit d0960f1

10 files changed

+659
-8
lines changed

Diff for: packages/@aws-cdk/aws-iotevents/README.md

+19-4
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,30 @@ Import it into your code:
4040
import * as iotevents from '@aws-cdk/aws-iotevents';
4141
```
4242

43-
## `Input`
43+
## `DetectorModel`
4444

45-
Add an AWS IoT Events input to your stack:
45+
The following example creates an AWS IoT Events detector model to your stack.
46+
The detector model need a reference to at least one AWS IoT Events input.
47+
AWS IoT Events inputs enable the detector to get MQTT payload values from IoT Core rules.
4648

4749
```ts
4850
import * as iotevents from '@aws-cdk/aws-iotevents';
4951

50-
new iotevents.Input(this, 'MyInput', {
51-
inputName: 'my_input',
52+
const input = new iotevents.Input(this, 'MyInput', {
53+
inputName: 'my_input', // optional
5254
attributeJsonPaths: ['payload.temperature'],
5355
});
56+
57+
const onlineState = new iotevents.State({
58+
stateName: 'online',
59+
onEnter: [{
60+
eventName: 'test-event',
61+
condition: iotevents.Expression.currentInput(input),
62+
}],
63+
});
64+
65+
new iotevents.DetectorModel(this, 'MyDetectorModel', {
66+
detectorModelName: 'test-detector-model', // optional
67+
initialState: onlineState,
68+
});
5469
```
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import { Resource, IResource } from '@aws-cdk/core';
3+
import { Construct } from 'constructs';
4+
import { CfnDetectorModel } from './iotevents.generated';
5+
import { State } from './state';
6+
7+
/**
8+
* Represents an AWS IoT Events detector model
9+
*/
10+
export interface IDetectorModel extends IResource {
11+
/**
12+
* The name of the detector model.
13+
*
14+
* @attribute
15+
*/
16+
readonly detectorModelName: string;
17+
}
18+
19+
/**
20+
* Properties for defining an AWS IoT Events detector model
21+
*/
22+
export interface DetectorModelProps {
23+
/**
24+
* The name of the detector model.
25+
*
26+
* @default - CloudFormation will generate a unique name of the detector model
27+
*/
28+
readonly detectorModelName?: string;
29+
30+
/**
31+
* The state that is entered at the creation of each detector.
32+
*/
33+
readonly initialState: State;
34+
35+
/**
36+
* The role that grants permission to AWS IoT Events to perform its operations.
37+
*
38+
* @default - a role will be created with default permissions
39+
*/
40+
readonly role?: iam.IRole;
41+
}
42+
43+
/**
44+
* Defines an AWS IoT Events detector model in this stack.
45+
*/
46+
export class DetectorModel extends Resource implements IDetectorModel {
47+
/**
48+
* Import an existing detector model.
49+
*/
50+
public static fromDetectorModelName(scope: Construct, id: string, detectorModelName: string): IDetectorModel {
51+
return new class extends Resource implements IDetectorModel {
52+
public readonly detectorModelName = detectorModelName;
53+
}(scope, id);
54+
}
55+
56+
public readonly detectorModelName: string;
57+
58+
constructor(scope: Construct, id: string, props: DetectorModelProps) {
59+
super(scope, id, {
60+
physicalName: props.detectorModelName,
61+
});
62+
63+
if (!props.initialState._onEnterEventsHaveAtLeastOneCondition()) {
64+
throw new Error('Detector Model must have at least one Input with a condition');
65+
}
66+
67+
const role = props.role ?? new iam.Role(this, 'DetectorModelRole', {
68+
assumedBy: new iam.ServicePrincipal('iotevents.amazonaws.com'),
69+
});
70+
71+
const resource = new CfnDetectorModel(this, 'Resource', {
72+
detectorModelName: this.physicalName,
73+
detectorModelDefinition: {
74+
initialStateName: props.initialState.stateName,
75+
states: [props.initialState._toStateJson()],
76+
},
77+
roleArn: role.roleArn,
78+
});
79+
80+
this.detectorModelName = this.getResourceNameAttribute(resource.ref);
81+
}
82+
}

Diff for: packages/@aws-cdk/aws-iotevents/lib/event.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Expression } from './expression';
2+
3+
/**
4+
* Specifies the actions to be performed when the condition evaluates to TRUE.
5+
*/
6+
export interface Event {
7+
/**
8+
* The name of the event.
9+
*/
10+
readonly eventName: string;
11+
12+
/**
13+
* The Boolean expression that, when TRUE, causes the actions to be performed.
14+
*
15+
* @default - none (the actions are always executed)
16+
*/
17+
readonly condition?: Expression;
18+
}

Diff for: packages/@aws-cdk/aws-iotevents/lib/expression.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { IInput } from './input';
2+
3+
/**
4+
* Expression for events in Detector Model state
5+
* @see https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-expressions.html
6+
*/
7+
export abstract class Expression {
8+
/**
9+
* Create a expression from the given string
10+
*/
11+
public static fromString(value: string): Expression {
12+
return new StringExpression(value);
13+
}
14+
15+
/**
16+
* Create a expression for function `currentInput()`.
17+
* It is evaluated to true if the specified input message was received.
18+
*/
19+
public static currentInput(input: IInput): Expression {
20+
return this.fromString(`currentInput("${input.inputName}")`);
21+
}
22+
23+
/**
24+
* Create a expression for get an input attribute as `$input.TemperatureInput.temperatures[2]`.
25+
*/
26+
public static inputAttribute(input: IInput, path: string): Expression {
27+
return this.fromString(`$input.${input.inputName}.${path}`);
28+
}
29+
30+
/**
31+
* Create a expression for the Equal operator
32+
*/
33+
public static eq(left: Expression, right: Expression): Expression {
34+
return new BinaryOperationExpression(left, '==', right);
35+
}
36+
37+
/**
38+
* Create a expression for the AND operator
39+
*/
40+
public static and(left: Expression, right: Expression): Expression {
41+
return new BinaryOperationExpression(left, '&&', right);
42+
}
43+
44+
constructor() {
45+
}
46+
47+
/**
48+
* this is called to evaluate the expression
49+
*/
50+
public abstract evaluate(): string;
51+
}
52+
53+
class StringExpression extends Expression {
54+
constructor(private readonly value: string) {
55+
super();
56+
}
57+
58+
public evaluate() {
59+
return this.value;
60+
}
61+
}
62+
63+
class BinaryOperationExpression extends Expression {
64+
constructor(
65+
private readonly left: Expression,
66+
private readonly operator: string,
67+
private readonly right: Expression,
68+
) {
69+
super();
70+
}
71+
72+
public evaluate() {
73+
return `${this.left.evaluate()} ${this.operator} ${this.right.evaluate()}`;
74+
}
75+
}

Diff for: packages/@aws-cdk/aws-iotevents/lib/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
export * from './detector-model';
2+
export * from './event';
3+
export * from './expression';
14
export * from './input';
5+
export * from './state';
26

37
// AWS::IoTEvents CloudFormation Resources:
48
export * from './iotevents.generated';

Diff for: packages/@aws-cdk/aws-iotevents/lib/state.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Event } from './event';
2+
import { CfnDetectorModel } from './iotevents.generated';
3+
4+
/**
5+
* Properties for defining a state of a detector
6+
*/
7+
export interface StateProps {
8+
/**
9+
* The name of the state.
10+
*/
11+
readonly stateName: string;
12+
13+
/**
14+
* Specifies the events on enter. the conditions of the events are evaluated when the state is entered.
15+
* If the condition is `TRUE`, the actions of the event are performed.
16+
*
17+
* @default - events on enter will not be set
18+
*/
19+
readonly onEnter?: Event[];
20+
}
21+
22+
/**
23+
* Defines a state of a detector
24+
*/
25+
export class State {
26+
/**
27+
* The name of the state
28+
*/
29+
public readonly stateName: string;
30+
31+
constructor(private readonly props: StateProps) {
32+
this.stateName = props.stateName;
33+
}
34+
35+
/**
36+
* Return the state property JSON
37+
*
38+
* @internal
39+
*/
40+
public _toStateJson(): CfnDetectorModel.StateProperty {
41+
const { stateName, onEnter } = this.props;
42+
return {
43+
stateName,
44+
onEnter: onEnter && { events: getEventJson(onEnter) },
45+
};
46+
}
47+
48+
/**
49+
* returns true if this state has at least one condition via events
50+
*
51+
* @internal
52+
*/
53+
public _onEnterEventsHaveAtLeastOneCondition(): boolean {
54+
return this.props.onEnter?.some(event => event.condition) ?? false;
55+
}
56+
}
57+
58+
function getEventJson(events: Event[]): CfnDetectorModel.EventProperty[] {
59+
return events.map(e => {
60+
return {
61+
eventName: e.eventName,
62+
condition: e.condition?.evaluate(),
63+
};
64+
});
65+
}

Diff for: packages/@aws-cdk/aws-iotevents/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,12 @@
8383
"jest": "^27.4.7"
8484
},
8585
"dependencies": {
86+
"@aws-cdk/aws-iam": "0.0.0",
8687
"@aws-cdk/core": "0.0.0",
8788
"constructs": "^3.3.69"
8889
},
8990
"peerDependencies": {
91+
"@aws-cdk/aws-iam": "0.0.0",
9092
"@aws-cdk/core": "0.0.0",
9193
"constructs": "^3.3.69"
9294
},

0 commit comments

Comments
 (0)