-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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*
- Loading branch information
Showing
10 changed files
with
659 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import * as iam from '@aws-cdk/aws-iam'; | ||
import { Resource, IResource } from '@aws-cdk/core'; | ||
import { Construct } from 'constructs'; | ||
import { CfnDetectorModel } from './iotevents.generated'; | ||
import { State } from './state'; | ||
|
||
/** | ||
* Represents an AWS IoT Events detector model | ||
*/ | ||
export interface IDetectorModel extends IResource { | ||
/** | ||
* The name of the detector model. | ||
* | ||
* @attribute | ||
*/ | ||
readonly detectorModelName: string; | ||
} | ||
|
||
/** | ||
* Properties for defining an AWS IoT Events detector model | ||
*/ | ||
export interface DetectorModelProps { | ||
/** | ||
* The name of the detector model. | ||
* | ||
* @default - CloudFormation will generate a unique name of the detector model | ||
*/ | ||
readonly detectorModelName?: string; | ||
|
||
/** | ||
* The state that is entered at the creation of each detector. | ||
*/ | ||
readonly initialState: State; | ||
|
||
/** | ||
* The role that grants permission to AWS IoT Events to perform its operations. | ||
* | ||
* @default - a role will be created with default permissions | ||
*/ | ||
readonly role?: iam.IRole; | ||
} | ||
|
||
/** | ||
* Defines an AWS IoT Events detector model in this stack. | ||
*/ | ||
export class DetectorModel extends Resource implements IDetectorModel { | ||
/** | ||
* Import an existing detector model. | ||
*/ | ||
public static fromDetectorModelName(scope: Construct, id: string, detectorModelName: string): IDetectorModel { | ||
return new class extends Resource implements IDetectorModel { | ||
public readonly detectorModelName = detectorModelName; | ||
}(scope, id); | ||
} | ||
|
||
public readonly detectorModelName: string; | ||
|
||
constructor(scope: Construct, id: string, props: DetectorModelProps) { | ||
super(scope, id, { | ||
physicalName: props.detectorModelName, | ||
}); | ||
|
||
if (!props.initialState._onEnterEventsHaveAtLeastOneCondition()) { | ||
throw new Error('Detector Model must have at least one Input with a condition'); | ||
} | ||
|
||
const role = props.role ?? new iam.Role(this, 'DetectorModelRole', { | ||
assumedBy: new iam.ServicePrincipal('iotevents.amazonaws.com'), | ||
}); | ||
|
||
const resource = new CfnDetectorModel(this, 'Resource', { | ||
detectorModelName: this.physicalName, | ||
detectorModelDefinition: { | ||
initialStateName: props.initialState.stateName, | ||
states: [props.initialState._toStateJson()], | ||
}, | ||
roleArn: role.roleArn, | ||
}); | ||
|
||
this.detectorModelName = this.getResourceNameAttribute(resource.ref); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Expression } from './expression'; | ||
|
||
/** | ||
* Specifies the actions to be performed when the condition evaluates to TRUE. | ||
*/ | ||
export interface Event { | ||
/** | ||
* The name of the event. | ||
*/ | ||
readonly eventName: string; | ||
|
||
/** | ||
* The Boolean expression that, when TRUE, causes the actions to be performed. | ||
* | ||
* @default - none (the actions are always executed) | ||
*/ | ||
readonly condition?: Expression; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { IInput } from './input'; | ||
|
||
/** | ||
* Expression for events in Detector Model state | ||
* @see https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-expressions.html | ||
*/ | ||
export abstract class Expression { | ||
/** | ||
* Create a expression from the given string | ||
*/ | ||
public static fromString(value: string): Expression { | ||
return new StringExpression(value); | ||
} | ||
|
||
/** | ||
* Create a expression for function `currentInput()`. | ||
* It is evaluated to true if the specified input message was received. | ||
*/ | ||
public static currentInput(input: IInput): Expression { | ||
return this.fromString(`currentInput("${input.inputName}")`); | ||
} | ||
|
||
/** | ||
* Create a expression for get an input attribute as `$input.TemperatureInput.temperatures[2]`. | ||
*/ | ||
public static inputAttribute(input: IInput, path: string): Expression { | ||
return this.fromString(`$input.${input.inputName}.${path}`); | ||
} | ||
|
||
/** | ||
* Create a expression for the Equal operator | ||
*/ | ||
public static eq(left: Expression, right: Expression): Expression { | ||
return new BinaryOperationExpression(left, '==', right); | ||
} | ||
|
||
/** | ||
* Create a expression for the AND operator | ||
*/ | ||
public static and(left: Expression, right: Expression): Expression { | ||
return new BinaryOperationExpression(left, '&&', right); | ||
} | ||
|
||
constructor() { | ||
} | ||
|
||
/** | ||
* this is called to evaluate the expression | ||
*/ | ||
public abstract evaluate(): string; | ||
} | ||
|
||
class StringExpression extends Expression { | ||
constructor(private readonly value: string) { | ||
super(); | ||
} | ||
|
||
public evaluate() { | ||
return this.value; | ||
} | ||
} | ||
|
||
class BinaryOperationExpression extends Expression { | ||
constructor( | ||
private readonly left: Expression, | ||
private readonly operator: string, | ||
private readonly right: Expression, | ||
) { | ||
super(); | ||
} | ||
|
||
public evaluate() { | ||
return `${this.left.evaluate()} ${this.operator} ${this.right.evaluate()}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
export * from './detector-model'; | ||
export * from './event'; | ||
export * from './expression'; | ||
export * from './input'; | ||
export * from './state'; | ||
|
||
// AWS::IoTEvents CloudFormation Resources: | ||
export * from './iotevents.generated'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { Event } from './event'; | ||
import { CfnDetectorModel } from './iotevents.generated'; | ||
|
||
/** | ||
* Properties for defining a state of a detector | ||
*/ | ||
export interface StateProps { | ||
/** | ||
* The name of the state. | ||
*/ | ||
readonly stateName: string; | ||
|
||
/** | ||
* Specifies the events on enter. the conditions of the events are evaluated when the state is entered. | ||
* If the condition is `TRUE`, the actions of the event are performed. | ||
* | ||
* @default - events on enter will not be set | ||
*/ | ||
readonly onEnter?: Event[]; | ||
} | ||
|
||
/** | ||
* Defines a state of a detector | ||
*/ | ||
export class State { | ||
/** | ||
* The name of the state | ||
*/ | ||
public readonly stateName: string; | ||
|
||
constructor(private readonly props: StateProps) { | ||
this.stateName = props.stateName; | ||
} | ||
|
||
/** | ||
* Return the state property JSON | ||
* | ||
* @internal | ||
*/ | ||
public _toStateJson(): CfnDetectorModel.StateProperty { | ||
const { stateName, onEnter } = this.props; | ||
return { | ||
stateName, | ||
onEnter: onEnter && { events: getEventJson(onEnter) }, | ||
}; | ||
} | ||
|
||
/** | ||
* returns true if this state has at least one condition via events | ||
* | ||
* @internal | ||
*/ | ||
public _onEnterEventsHaveAtLeastOneCondition(): boolean { | ||
return this.props.onEnter?.some(event => event.condition) ?? false; | ||
} | ||
} | ||
|
||
function getEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { | ||
return events.map(e => { | ||
return { | ||
eventName: e.eventName, | ||
condition: e.condition?.evaluate(), | ||
}; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.