Skip to content

Commit

Permalink
feat(iot): add Action to set a CloudWatch alarm (aws#18021)
Browse files Browse the repository at this point in the history
Adding IoT Rule action for CloudWatch alarm.

Fixes aws#17705

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jerry-shao authored and TikiTDO committed Feb 21, 2022
1 parent 6bb75c3 commit aab7c27
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 0 deletions.
33 changes: 33 additions & 0 deletions packages/@aws-cdk/aws-iot-actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Currently supported are:
- Put objects to a S3 bucket
- Put logs to CloudWatch Logs
- Capture CloudWatch metrics
- Change state for a CloudWatch alarm
- Put records to Kinesis Data Firehose stream

## Invoke a Lambda function
Expand Down Expand Up @@ -149,6 +150,38 @@ const topicRule = new iot.TopicRule(this, 'TopicRule', {
});
```

## Change the state of an Amazon CloudWatch alarm

The code snippet below creates an AWS IoT Rule that changes the state of an Amazon CloudWatch alarm when it is triggered:

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

const metric = new cloudwatch.Metric({
namespace: 'MyNamespace',
metricName: 'MyMetric',
dimensions: { MyDimension: 'MyDimensionValue' },
});
const alarm = new cloudwatch.Alarm(this, 'MyAlarm', {
metric: metric,
threshold: 100,
evaluationPeriods: 3,
datapointsToAlarm: 2,
});

const topicRule = new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"),
actions: [
new actions.CloudWatchSetAlarmStateAction(alarm, {
reason: 'AWS Iot Rule action is triggered',
alarmStateToSet: cloudwatch.AlarmState.ALARM,
}),
],
});
```

## Put records to Kinesis Data Firehose stream

The code snippet below creates an AWS IoT Rule that put records to Put records
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as iot from '@aws-cdk/aws-iot';
import { CommonActionProps } from './common-action-props';
import { singletonActionRole } from './private/role';

/**
* Configuration properties of an action for CloudWatch alarm.
*/
export interface CloudWatchSetAlarmStateActionProps extends CommonActionProps {
/**
* The reason for the alarm change.
*
* @default None
*/
readonly reason?: string;

/**
* The value of the alarm state to set.
*/
readonly alarmStateToSet: cloudwatch.AlarmState;
}

/**
* The action to change the state of an Amazon CloudWatch alarm.
*/
export class CloudWatchSetAlarmStateAction implements iot.IAction {
constructor(
private readonly alarm: cloudwatch.IAlarm,
private readonly props: CloudWatchSetAlarmStateActionProps,
) {
}

bind(topicRule: iot.ITopicRule): iot.ActionConfig {
const role = this.props.role ?? singletonActionRole(topicRule);
role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['cloudwatch:SetAlarmState'],
resources: [this.alarm.alarmArn],
}));

return {
configuration: {
cloudwatchAlarm: {
alarmName: this.alarm.alarmName,
roleArn: role.roleArn,
stateReason: this.props.reason ?? `Set state of '${this.alarm.alarmName}' to '${this.props.alarmStateToSet}'`,
stateValue: this.props.alarmStateToSet,
},
},
};
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-iot-actions/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './cloudwatch-logs-action';
export * from './cloudwatch-put-metric-action';
export * from './cloudwatch-set-alarm-state-action';
export * from './common-action-props';
export * from './firehose-stream-action';
export * from './lambda-function-action';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Template, Match } from '@aws-cdk/assertions';
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as iot from '@aws-cdk/aws-iot';
import * as cdk from '@aws-cdk/core';
import * as actions from '../../lib';

test('Default cloudwatch alarm action', () => {
// Given
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, stateReason, stateValue FROM 'device/+/data'"),
});
const alarm = cloudwatch.Alarm.fromAlarmArn(stack, 'MyAlarm', 'arn:aws:cloudwatch:us-east-1:123456789012:alarm:MyAlarm');

// When
topicRule.addAction(new actions.CloudWatchSetAlarmStateAction(alarm, {
reason: 'Test reason',
alarmStateToSet: cloudwatch.AlarmState.ALARM,
}));

// Then
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
CloudwatchAlarm: {
AlarmName: 'MyAlarm',
RoleArn: {
'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'],
},
StateReason: 'Test reason',
StateValue: cloudwatch.AlarmState.ALARM,
},
},
],
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'iot.amazonaws.com',
},
},
],
Version: '2012-10-17',
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'cloudwatch:SetAlarmState',
Effect: 'Allow',
Resource: alarm.alarmArn,
},
],
Version: '2012-10-17',
},
PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7',
Roles: [{ Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }],
});
});

test('can set role', () => {
// Given
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, stateReason, stateValue FROM 'device/+/data'"),
});

// When
topicRule.addAction(new actions.CloudWatchSetAlarmStateAction(
cloudwatch.Alarm.fromAlarmArn(stack, 'MyAlarm', 'arn:aws:cloudwatch:us-east-1:123456789012:alarm:MyAlarm'),
{
reason: '${stateReason}',
alarmStateToSet: cloudwatch.AlarmState.ALARM,
role: iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'),
},
));

// Then
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
Match.objectLike({ CloudwatchAlarm: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }),
],
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyName: 'MyRolePolicy64AB00A5',
Roles: ['ForTest'],
});
});

test('set default reason', () => {
// Given
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, stateReason, stateValue FROM 'device/+/data'"),
});
const alarm = cloudwatch.Alarm.fromAlarmArn(stack, 'MyAlarm', 'arn:aws:cloudwatch:us-east-1:123456789012:alarm:MyAlarm');

// When
topicRule.addAction(new actions.CloudWatchSetAlarmStateAction(alarm, {
alarmStateToSet: cloudwatch.AlarmState.ALARM,
}));

// Then
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
CloudwatchAlarm: {
AlarmName: 'MyAlarm',
RoleArn: {
'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'],
},
StateReason: "Set state of 'MyAlarm' to 'ALARM'",
StateValue: cloudwatch.AlarmState.ALARM,
},
},
],
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"Resources": {
"MyAlarm696658B6": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"EvaluationPeriods": 3,
"DatapointsToAlarm": 2,
"Dimensions": [
{
"Name": "MyDimension",
"Value": "MyDimensionValue"
}
],
"MetricName": "MyMetric",
"Namespace": "MyNamespace",
"Period": 300,
"Statistic": "Average",
"Threshold": 100
}
},
"TopicRule40A4EA44": {
"Type": "AWS::IoT::TopicRule",
"Properties": {
"TopicRulePayload": {
"Actions": [
{
"CloudwatchAlarm": {
"AlarmName": {
"Ref": "MyAlarm696658B6"
},
"RoleArn": {
"Fn::GetAtt": [
"TopicRuleTopicRuleActionRole246C4F77",
"Arn"
]
},
"StateReason": {
"Fn::Join": [
"",
[
"Set state of '",
{
"Ref": "MyAlarm696658B6"
},
"' to 'ALARM'"
]
]
},
"StateValue": "ALARM"
}
}
],
"AwsIotSqlVersion": "2016-03-23",
"Sql": "SELECT topic(2) as device_id FROM 'device/+/data'"
}
}
},
"TopicRuleTopicRuleActionRole246C4F77": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "iot.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "cloudwatch:SetAlarmState",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"MyAlarm696658B6",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687",
"Roles": [
{
"Ref": "TopicRuleTopicRuleActionRole246C4F77"
}
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iot from '@aws-cdk/aws-iot';
import * as cdk from '@aws-cdk/core';
import * as actions 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);

const metric = new cloudwatch.Metric({
namespace: 'MyNamespace',
metricName: 'MyMetric',
dimensions: { MyDimension: 'MyDimensionValue' },
});
const alarm = new cloudwatch.Alarm(this, 'MyAlarm', {
metric: metric,
threshold: 100,
evaluationPeriods: 3,
datapointsToAlarm: 2,
});
const topicRule = new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"),
});

topicRule.addAction(new actions.CloudWatchSetAlarmStateAction(alarm, {
alarmStateToSet: cloudwatch.AlarmState.ALARM,
}));
}
}

new TestStack(app, 'test-stack');
app.synth();

0 comments on commit aab7c27

Please sign in to comment.