Skip to content

Commit 319cfcd

Browse files
authored
feat(cloudwatch): EC2 actions (#13281)
Fixes #13228 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 9331657 commit 319cfcd

File tree

6 files changed

+155
-0
lines changed

6 files changed

+155
-0
lines changed

packages/@aws-cdk/aws-cloudwatch-actions/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,19 @@
1111

1212
This library contains a set of classes which can be used as CloudWatch Alarm actions.
1313

14+
The currently implemented actions are: EC2 Actions, SNS Actions, Autoscaling Actions and Aplication Autoscaling Actions
15+
16+
17+
## EC2 Action Example
18+
19+
```ts
20+
import * as cw from "@aws-cdk/aws-cloudwatch";
21+
// Alarm must be configured with an EC2 per-instance metric
22+
let alarm: cw.Alarm;
23+
// Attach a reboot when alarm triggers
24+
alarm.addAlarmAction(
25+
new Ec2Action(Ec2InstanceActions.REBOOT)
26+
);
27+
```
28+
1429
See `@aws-cdk/aws-cloudwatch` for more information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
2+
import { Stack } from '@aws-cdk/core';
3+
4+
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
5+
// eslint-disable-next-line no-duplicate-imports, import/order
6+
import { Construct } from '@aws-cdk/core';
7+
8+
/**
9+
* Types of EC2 actions available
10+
*/
11+
export enum Ec2InstanceAction {
12+
/**
13+
* Stop the instance
14+
*/
15+
STOP = 'stop',
16+
/**
17+
* Terminatethe instance
18+
*/
19+
TERMINATE = 'terminate',
20+
/**
21+
* Recover the instance
22+
*/
23+
RECOVER = 'recover',
24+
/**
25+
* Reboot the instance
26+
*/
27+
REBOOT = 'reboot'
28+
}
29+
30+
/**
31+
* Use an EC2 action as an Alarm action
32+
*/
33+
export class Ec2Action implements cloudwatch.IAlarmAction {
34+
private ec2Action: Ec2InstanceAction;
35+
36+
constructor(instanceAction: Ec2InstanceAction) {
37+
this.ec2Action = instanceAction;
38+
}
39+
40+
/**
41+
* Returns an alarm action configuration to use an EC2 action as an alarm action
42+
*/
43+
bind(_scope: Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig {
44+
return { alarmActionArn: `arn:aws:automate:${Stack.of(_scope).region}:ec2:${this.ec2Action}` };
45+
}
46+
}
47+
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './appscaling';
22
export * from './autoscaling';
33
export * from './sns';
4+
export * from './ec2';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import '@aws-cdk/assert/jest';
2+
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
3+
import { Stack } from '@aws-cdk/core';
4+
import * as actions from '../lib';
5+
6+
test('can use instance reboot as alarm action', () => {
7+
// GIVEN
8+
const stack = new Stack();
9+
const alarm = new cloudwatch.Alarm(stack, 'Alarm', {
10+
metric: new cloudwatch.Metric({
11+
namespace: 'AWS/EC2',
12+
metricName: 'StatusCheckFailed',
13+
dimensions: {
14+
InstanceId: 'i-03cb889aaaafffeee',
15+
},
16+
}),
17+
evaluationPeriods: 3,
18+
threshold: 100,
19+
});
20+
21+
// WHEN
22+
alarm.addAlarmAction(new actions.Ec2Action(actions.Ec2InstanceAction.REBOOT));
23+
24+
// THEN
25+
expect(stack).toHaveResource('AWS::CloudWatch::Alarm', {
26+
AlarmActions: [
27+
{
28+
'Fn::Join': [
29+
'',
30+
[
31+
'arn:aws:automate:',
32+
{
33+
Ref: 'AWS::Region',
34+
},
35+
':ec2:reboot',
36+
],
37+
],
38+
},
39+
],
40+
});
41+
});

packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts

+28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Lazy, Stack, Token } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
3+
import { IAlarmAction } from './alarm-action';
34
import { AlarmBase, IAlarm } from './alarm-base';
45
import { CfnAlarm, CfnAlarmProps } from './cloudwatch.generated';
56
import { HorizontalAnnotation } from './graph';
@@ -224,6 +225,33 @@ export class Alarm extends AlarmBase {
224225
return this.annotation;
225226
}
226227

228+
/**
229+
* Trigger this action if the alarm fires
230+
*
231+
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
232+
*/
233+
public addAlarmAction(...actions: IAlarmAction[]) {
234+
if (this.alarmActionArns === undefined) {
235+
this.alarmActionArns = [];
236+
}
237+
238+
this.alarmActionArns.push(...actions.map(a =>
239+
this.validateActionArn(a.bind(this, this).alarmActionArn),
240+
));
241+
}
242+
243+
private validateActionArn(actionArn: string): string {
244+
const ec2ActionsRegexp: RegExp = /arn:aws:automate:[a-z|\d|-]+:ec2:[a-z]+/;
245+
if (ec2ActionsRegexp.test(actionArn)) {
246+
// Check per-instance metric
247+
const metricConfig = this.metric.toMetricConfig();
248+
if (metricConfig.metricStat?.dimensions?.length != 1 || metricConfig.metricStat?.dimensions![0].name != 'InstanceId') {
249+
throw new Error(`EC2 alarm actions requires an EC2 Per-Instance Metric. (${JSON.stringify(metricConfig)} does not have an 'InstanceId' dimension)`);
250+
}
251+
}
252+
return actionArn;
253+
}
254+
227255
private renderMetric(metric: IMetric) {
228256
const self = this;
229257
return dispatchMetric(metric, {

packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts

+23
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ export = {
4242

4343
test.done();
4444
},
45+
'non ec2 instance related alarm does not accept EC2 action'(test: Test) {
46+
47+
const stack = new Stack();
48+
const alarm = new Alarm(stack, 'Alarm', {
49+
metric: testMetric,
50+
threshold: 1000,
51+
evaluationPeriods: 2,
52+
});
53+
54+
test.throws(() => {
55+
alarm.addAlarmAction(new Ec2TestAlarmAction('arn:aws:automate:us-east-1:ec2:reboot'));
56+
}, /EC2 alarm actions requires an EC2 Per-Instance Metric. \(.+ does not have an 'InstanceId' dimension\)/);
57+
test.done();
58+
},
4559
'can make simple alarm'(test: Test) {
4660
// GIVEN
4761
const stack = new Stack();
@@ -253,3 +267,12 @@ class TestAlarmAction implements IAlarmAction {
253267
return { alarmActionArn: this.arn };
254268
}
255269
}
270+
271+
class Ec2TestAlarmAction implements IAlarmAction {
272+
constructor(private readonly arn: string) {
273+
}
274+
275+
public bind(_scope: Construct, _alarm: IAlarm) {
276+
return { alarmActionArn: this.arn };
277+
}
278+
}

0 commit comments

Comments
 (0)