Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iot): add Action to set a CloudWatch alarm #18021

Merged
merged 5 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();