From ac3ca30dcda608a11cdd4c695b0573281d7b0831 Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Thu, 25 Feb 2021 10:39:13 +0100 Subject: [PATCH 1/8] feat(cw-actions) Add ec2 actions --- .../aws-cloudwatch-actions/lib/ec2.ts | 47 +++++++++++++++++++ .../aws-cloudwatch-actions/lib/index.ts | 1 + .../aws-cloudwatch-actions/test/ec2.test.ts | 36 ++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts create mode 100644 packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts new file mode 100644 index 0000000000000..348ab930cc092 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts @@ -0,0 +1,47 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { Stack } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +/** + * Types of EC2 actions available + */ +export enum Ec2InstanceActions { + /** + * Stop the instance + */ + STOP = 'stop', + /** + * Terminatethe instance + */ + TERMINATE = 'terminate', + /** + * Recover the instance + */ + RECOVER = 'recover', + /** + * Reboot the instance + */ + REBOOT = 'reboot' +} + +/** + * Use an EC2 action as an Alarm action + */ +export class Ec2Action implements cloudwatch.IAlarmAction { + private ec2Action: string; + + constructor(instanceAction: Ec2InstanceActions) { + this.ec2Action = instanceAction; + } + + /** + * Returns an alarm action configuration to use an EC2 action as an alarm action + */ + bind(_scope: Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { + return { alarmActionArn: `arn:aws:automate:${Stack.of(_scope).region}:ec2:${this.ec2Action}` }; + } +} + diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/index.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/index.ts index 3c446d47491aa..5a384eba01247 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/index.ts @@ -1,3 +1,4 @@ export * from './appscaling'; export * from './autoscaling'; export * from './sns'; +export * from './ec2'; diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts new file mode 100644 index 0000000000000..bdbb7de4908c1 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts @@ -0,0 +1,36 @@ +import '@aws-cdk/assert/jest'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { Stack } from '@aws-cdk/core'; +import * as actions from '../lib'; + +test('can use instance reboot as alarm action', () => { + // GIVEN + const stack = new Stack(); + //const topic = new sns.Topic(stack, 'Topic'); + const alarm = new cloudwatch.Alarm(stack, 'Alarm', { + metric: new cloudwatch.Metric({ namespace: 'AWS', metricName: 'Henk' }), + evaluationPeriods: 3, + threshold: 100, + }); + + // WHEN + alarm.addAlarmAction(new actions.Ec2Action(actions.Ec2InstanceActions.REBOOT)); + + // THEN + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { + AlarmActions: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:automate:', + { + Ref: 'AWS::Region', + }, + ':ec2:reboot', + ], + ], + }, + ], + }); +}); From 32546518c013334b2020cc9b89cb892dc85a926c Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Thu, 25 Feb 2021 10:47:49 +0100 Subject: [PATCH 2/8] edit README --- packages/@aws-cdk/aws-cloudwatch-actions/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/README.md b/packages/@aws-cdk/aws-cloudwatch-actions/README.md index 03d84220c5c08..445dbf6c79551 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/README.md +++ b/packages/@aws-cdk/aws-cloudwatch-actions/README.md @@ -11,4 +11,5 @@ This library contains a set of classes which can be used as CloudWatch Alarm actions. +The currently implemented actions are: EC2 Actions, SNS Actions, Autoscaling Actions and Aplication Autoscaling Actions See `@aws-cdk/aws-cloudwatch` for more information. From 699bef7d7ca2a68b2e744324611b8b18d3d6eeea Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Thu, 25 Feb 2021 15:01:57 +0100 Subject: [PATCH 3/8] fix naming issues --- packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts | 6 +++--- packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts index 348ab930cc092..57d5a4fb67501 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/ec2.ts @@ -8,7 +8,7 @@ import { Construct } from '@aws-cdk/core'; /** * Types of EC2 actions available */ -export enum Ec2InstanceActions { +export enum Ec2InstanceAction { /** * Stop the instance */ @@ -31,9 +31,9 @@ export enum Ec2InstanceActions { * Use an EC2 action as an Alarm action */ export class Ec2Action implements cloudwatch.IAlarmAction { - private ec2Action: string; + private ec2Action: Ec2InstanceAction; - constructor(instanceAction: Ec2InstanceActions) { + constructor(instanceAction: Ec2InstanceAction) { this.ec2Action = instanceAction; } diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts index bdbb7de4908c1..39ef22ea011cb 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts @@ -14,7 +14,7 @@ test('can use instance reboot as alarm action', () => { }); // WHEN - alarm.addAlarmAction(new actions.Ec2Action(actions.Ec2InstanceActions.REBOOT)); + alarm.addAlarmAction(new actions.Ec2Action(actions.Ec2InstanceAction.REBOOT)); // THEN expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { From d7225791236d9c3f4749a2c0fd4026d02de96dd2 Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Thu, 25 Feb 2021 23:27:39 +0100 Subject: [PATCH 4/8] test modified --- .../@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts index 39ef22ea011cb..8845588d533b4 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts @@ -6,9 +6,14 @@ import * as actions from '../lib'; test('can use instance reboot as alarm action', () => { // GIVEN const stack = new Stack(); - //const topic = new sns.Topic(stack, 'Topic'); const alarm = new cloudwatch.Alarm(stack, 'Alarm', { - metric: new cloudwatch.Metric({ namespace: 'AWS', metricName: 'Henk' }), + metric: new cloudwatch.Metric({ + namespace: 'AWS/EC2', + metricName: 'StatusCheckFailed', + dimensions: { + InstanceId: 'i-03cb889aaaafffeee', + }, + }), evaluationPeriods: 3, threshold: 100, }); From a4d03c028a450f915e146d29ff9cb647ee03b863 Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Thu, 25 Feb 2021 23:27:45 +0100 Subject: [PATCH 5/8] readme updated --- packages/@aws-cdk/aws-cloudwatch-actions/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/README.md b/packages/@aws-cdk/aws-cloudwatch-actions/README.md index 445dbf6c79551..f13861a8c1555 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/README.md +++ b/packages/@aws-cdk/aws-cloudwatch-actions/README.md @@ -12,4 +12,18 @@ This library contains a set of classes which can be used as CloudWatch Alarm actions. The currently implemented actions are: EC2 Actions, SNS Actions, Autoscaling Actions and Aplication Autoscaling Actions + + +## EC2 Action Example + +```ts +import * as cw from "@aws-cdk/aws-cloudwatch"; +// Alarm must be configured with an EC2 per-instance metric +let alarm: cw.Alarm; +// Attach a reboot when alarm triggers +alarm.addAlarmAction( + new Ec2Action(Ec2InstanceActions.REBOOT) +); +``` + See `@aws-cdk/aws-cloudwatch` for more information. From c3ac5c88b6de8c3a6b743e66575d66e5ea401f9d Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Sun, 28 Feb 2021 10:53:58 +0100 Subject: [PATCH 6/8] check per instance metric --- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 26 +++++++++++++++++++ .../aws-cloudwatch/test/test.alarm.ts | 23 ++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 5c1daacb54dd3..6bbc319dd65de 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,5 +1,6 @@ import { Lazy, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { IAlarmAction } from './alarm-action'; import { AlarmBase, IAlarm } from './alarm-base'; import { CfnAlarm, CfnAlarmProps } from './cloudwatch.generated'; import { HorizontalAnnotation } from './graph'; @@ -224,6 +225,31 @@ export class Alarm extends AlarmBase { return this.annotation; } + /** + * Trigger this action if the alarm fires + * + * Typically the ARN of an SNS topic or ARN of an AutoScaling policy. + */ + public addAlarmAction(...actions: IAlarmAction[]) { + if (this.alarmActionArns === undefined) { + this.alarmActionArns = []; + } + + this.alarmActionArns.push(...actions.map(a => { + const actionArn = a.bind(this, this).alarmActionArn; + + const ec2ActionsRegexp: RegExp = /arn:aws:automate:[a-z|\d|-]+:ec2:[a-z]+/; + if (ec2ActionsRegexp.test(actionArn)) { + // Check per-instance metric + const metricConfig = this.metric.toMetricConfig(); + if (metricConfig.metricStat?.dimensions?.length != 1 || metricConfig.metricStat?.dimensions![0].name != 'InstanceId') { + throw new Error('EC2 alarm actions must use an EC2 Per-Instance Metric'); + } + } + return actionArn; + })); + } + private renderMetric(metric: IMetric) { const self = this; return dispatchMetric(metric, { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts index c7c0f647c2e58..9b534656a9356 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts @@ -42,6 +42,20 @@ export = { test.done(); }, + 'non ec2 instance related alarm does not accept EC2 action'(test: Test) { + + const stack = new Stack(); + const alarm = new Alarm(stack, 'Alarm', { + metric: testMetric, + threshold: 1000, + evaluationPeriods: 2, + }); + + test.throws(() => { + alarm.addAlarmAction(new Ec2TestAlarmAction('arn:aws:automate:us-east-1:ec2:reboot')); + }, /EC2 alarm actions must use an EC2 Per-Instance Metric/); + test.done(); + }, 'can make simple alarm'(test: Test) { // GIVEN const stack = new Stack(); @@ -253,3 +267,12 @@ class TestAlarmAction implements IAlarmAction { return { alarmActionArn: this.arn }; } } + +class Ec2TestAlarmAction implements IAlarmAction { + constructor(private readonly arn: string) { + } + + public bind(_scope: Construct, _alarm: IAlarm) { + return { alarmActionArn: this.arn }; + } +} From 2bfd88dff7161515807a3cfe1e94c8ef408fac65 Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Wed, 3 Mar 2021 17:11:01 +0100 Subject: [PATCH 7/8] requested changes --- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 6bbc319dd65de..650b318b49d09 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -235,19 +235,21 @@ export class Alarm extends AlarmBase { this.alarmActionArns = []; } - this.alarmActionArns.push(...actions.map(a => { - const actionArn = a.bind(this, this).alarmActionArn; - - const ec2ActionsRegexp: RegExp = /arn:aws:automate:[a-z|\d|-]+:ec2:[a-z]+/; - if (ec2ActionsRegexp.test(actionArn)) { - // Check per-instance metric - const metricConfig = this.metric.toMetricConfig(); - if (metricConfig.metricStat?.dimensions?.length != 1 || metricConfig.metricStat?.dimensions![0].name != 'InstanceId') { - throw new Error('EC2 alarm actions must use an EC2 Per-Instance Metric'); - } + this.alarmActionArns.push(...actions.map(a => + this.validateActionArn(a.bind(this, this).alarmActionArn), + )); + } + + private validateActionArn(actionArn: string): string { + const ec2ActionsRegexp: RegExp = /arn:aws:automate:[a-z|\d|-]+:ec2:[a-z]+/; + if (ec2ActionsRegexp.test(actionArn)) { + // Check per-instance metric + const metricConfig = this.metric.toMetricConfig(); + if (metricConfig.metricStat?.dimensions?.length != 1 || metricConfig.metricStat?.dimensions![0].name != 'InstanceId') { + throw new Error(`EC2 alarm actions requires an EC2 Per-Instance Metric. (${JSON.stringify(metricConfig)} does not have an 'InstanceId' dimension)`); } - return actionArn; - })); + } + return actionArn; } private renderMetric(metric: IMetric) { From 02921034d5e156855da0dfc761d47cce2b8454f3 Mon Sep 17 00:00:00 2001 From: Carlos Yuste Date: Wed, 3 Mar 2021 17:36:12 +0100 Subject: [PATCH 8/8] fix test --- packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts index 9b534656a9356..c6a727c023c16 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts @@ -53,7 +53,7 @@ export = { test.throws(() => { alarm.addAlarmAction(new Ec2TestAlarmAction('arn:aws:automate:us-east-1:ec2:reboot')); - }, /EC2 alarm actions must use an EC2 Per-Instance Metric/); + }, /EC2 alarm actions requires an EC2 Per-Instance Metric. \(.+ does not have an 'InstanceId' dimension\)/); test.done(); }, 'can make simple alarm'(test: Test) {