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 3 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
34 changes: 34 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,39 @@ 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.
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved

```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' },
});

jerry-shao marked this conversation as resolved.
Show resolved Hide resolved
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 ?? 'AWS IoT Rule action triggers a change',
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved
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,145 @@
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,
},
),
);
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved

// 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'),
},
),
);
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved

// 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,
},
),
);
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved

// Then
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
CloudwatchAlarm: {
AlarmName: 'MyAlarm',
RoleArn: {
'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'],
},
StateReason: 'AWS IoT Rule action triggers a change',
StateValue: cloudwatch.AlarmState.ALARM,
},
},
],
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"Resources": {
"TopicRule40A4EA44": {
"Type": "AWS::IoT::TopicRule",
"Properties": {
"TopicRulePayload": {
"Actions": [
{
"CloudwatchAlarm": {
"AlarmName": {
"Ref": "MyAlarm696658B6"
},
"RoleArn": {
"Fn::GetAtt": [
"TopicRuleTopicRuleActionRole246C4F77",
"Arn"
]
},
"StateReason": "Test reason",
"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"
}
]
}
},
"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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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 topicRule = new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"),
});
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved

const metric = new cloudwatch.Metric({
namespace: 'MyNamespace',
metricName: 'MyMetric',
dimensions: { MyDimension: 'MyDimensionValue' },
});

jerry-shao marked this conversation as resolved.
Show resolved Hide resolved
const alarm = new cloudwatch.Alarm(this, 'MyAlarm', {
metric: metric,
threshold: 100,
evaluationPeriods: 3,
datapointsToAlarm: 2,
});

topicRule.addAction(new actions.CloudWatchSetAlarmStateAction(alarm, {
reason: 'Test reason',
jerry-shao marked this conversation as resolved.
Show resolved Hide resolved
alarmStateToSet: cloudwatch.AlarmState.ALARM,
}));
}
}

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