From f5a6c3e462bcb54ee1dce71c04e91b69269d322c Mon Sep 17 00:00:00 2001 From: Bob Wilkinson Date: Wed, 7 Aug 2019 12:25:31 -0700 Subject: [PATCH 1/3] Updated CloudWatch Alarm handling to support metric math alarms --- index.js | 30 +++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index c61ab4e..7f24e8c 100644 --- a/index.js +++ b/index.js @@ -233,12 +233,33 @@ var handleCloudWatch = function(event, context) { var region = event.Records[0].EventSubscriptionArn.split(":")[3]; var subject = "AWS CloudWatch Notification"; var alarmName = message.AlarmName; - var metricName = message.Trigger.MetricName; + var trigger = message.Trigger; + var alarmExpression; + if (typeof trigger.MetricName === "undefined") { + // This is a CloudWatch metric math alarm. Instead of a MetricName and + // statistic there is an array of Metrics where the first element is the + // math expression. + + // first build a dictionary mapping metric ids to metric name and statistic + var sourceMetrics = {}; + for (const metric of trigger.Metrics.slice(1)) { + sourceMetrics[metric.Id] = metric.MetricStat.Stat + ":" + + metric.MetricStat.Metric.MetricName; + } + + // now replace each instance of the metric id in the alarm expression + alarmExpression = trigger.Metrics[0].Expression; + for (var metricid in sourceMetrics) { + alarmExpression = alarmExpression.replace(new RegExp(metricid,"g"),sourceMetrics[metricid]); + } + } else { + // This is a standard CloudWatch alarm on a single metric + alarmExpression = trigger.Statistic + ":" + trigger.MetricName; + } var oldState = message.OldStateValue; var newState = message.NewStateValue; var alarmDescription = message.AlarmDescription; var alarmReason = message.NewStateReason; - var trigger = message.Trigger; var color = "warning"; if (message.NewStateValue === "ALARM") { @@ -257,8 +278,7 @@ var handleCloudWatch = function(event, context) { { "title": "Alarm Description", "value": alarmDescription, "short": false}, { "title": "Trigger", - "value": trigger.Statistic + " " - + metricName + " " + "value": alarmExpression + " " + trigger.ComparisonOperator + " " + trigger.Threshold + " for " + trigger.EvaluationPeriods + " period(s) of " @@ -364,7 +384,7 @@ var processEvent = function(event, context) { try { eventSnsMessage = JSON.parse(eventSnsMessageRaw); } - catch (e) { + catch (e) { } if(eventSubscriptionArn.indexOf(config.services.codepipeline.match_text) > -1 || eventSnsSubject.indexOf(config.services.codepipeline.match_text) > -1 || eventSnsMessageRaw.indexOf(config.services.codepipeline.match_text) > -1){ diff --git a/package.json b/package.json index 6e1e021..15d9e24 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lambda-cloudwatch-slack", "version": "0.3.0", "description": "Better Slack notifications for AWS CloudWatch", - "authors": [ + "authors": [ "Christopher Reichert ", "Cody Reichert ", "Alexandr Promakh " From 985d5197a4a4a0144f9920c2963cb21906e6d57d Mon Sep 17 00:00:00 2001 From: Bob Wilkinson Date: Wed, 7 Aug 2019 12:55:19 -0700 Subject: [PATCH 2/3] Added metric math alarm test case --- scripts/test.sh | 3 ++- test/sns-cloudwatch-event-metricmath.json | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/sns-cloudwatch-event-metricmath.json diff --git a/scripts/test.sh b/scripts/test.sh index c3e1b27..036a7de 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -5,9 +5,10 @@ $NODE_LAMBDA run -x test/context.json -j test/sns-codepipeline-event-stage-start $NODE_LAMBDA run -x test/context.json -j test/sns-codepipeline-event-stage-succeeded.json $NODE_LAMBDA run -x test/context.json -j test/sns-codepipeline-event-stage-failed.json $NODE_LAMBDA run -x test/context.json -j test/sns-cloudwatch-event.json +$NODE_LAMBDA run -x test/context.json -j test/sns-cloudwatch-event-metricmath.json $NODE_LAMBDA run -x test/context.json -j test/sns-event.json $NODE_LAMBDA run -x test/context.json -j test/sns-elastic-beanstalk-event.json $NODE_LAMBDA run -x test/context.json -j test/sns-codedeploy-event.json $NODE_LAMBDA run -x test/context.json -j test/sns-codedeploy-configuration.json $NODE_LAMBDA run -x test/context.json -j test/sns-elasticache-event.json -$NODE_LAMBDA run -x test/context.json -j test/sns-autoscaling-event.json \ No newline at end of file +$NODE_LAMBDA run -x test/context.json -j test/sns-autoscaling-event.json diff --git a/test/sns-cloudwatch-event-metricmath.json b/test/sns-cloudwatch-event-metricmath.json new file mode 100644 index 0000000..6eb0b42 --- /dev/null +++ b/test/sns-cloudwatch-event-metricmath.json @@ -0,0 +1,18 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:us-east-1:123456789123:cloudwatch-alarms:00000000-0000-0000-0000-000000000000", + "Sns": { + "Type": "Notification", + "MessageId": "00000000-0000-0000-0000-000000000000", + "TopicArn": "arn:aws:sns:us-east-1:123456789123:cloudwatch-alarms", + "Subject": "ALARM: \"Sample Metric Math Alert\" in US East (N. Virginia)", + "Message": "{\"AlarmName\":\"Sample Metric Math Alert\",\"AlarmDescription\":null,\"AWSAccountId\":\"946184294613\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [8133.333333333447 (07/08/19 18:43:00)] was greater than the threshold (1000.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2019-08-07T18:45:24.269+0000\",\"Region\":\"US East (N. Virginia)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":1000.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\",\"Metrics\":[{\"Expression\":\"m1*100*m2 - MIN(m1)\",\"Id\":\"e2\",\"Label\":\"Expression2\",\"ReturnData\":true},{\"Id\":\"m1\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"i-0a5c8ea70646e7b05\",\"name\":\"InstanceId\"}],\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false},{\"Id\":\"m2\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"i-0a5c8ea70646e7b05\",\"name\":\"InstanceId\"}],\"MetricName\":\"NetworkOut\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false}]}}", + "Timestamp": "2019-08-07T18:45:24.355Z", + "MessageAttributes": {} + } + } + ] +} From 5e4123c650e67b9a8234476e4158b5a4636be36a Mon Sep 17 00:00:00 2001 From: Bob Wilkinson Date: Thu, 8 Aug 2019 11:44:39 -0700 Subject: [PATCH 3/3] Addressing Comments on the Pull Request --- index.js | 25 ++++++++++++------------- package-lock.json | 5 +++++ package.json | 1 + 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 7f24e8c..ee970df 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ var url = require('url'); var https = require('https'); var config = require('./config'); var _ = require('lodash'); +var escapeStringRegexp = require('escape-string-regexp'); var hookUrl; var baseSlackMessage = {} @@ -235,22 +236,20 @@ var handleCloudWatch = function(event, context) { var alarmName = message.AlarmName; var trigger = message.Trigger; var alarmExpression; - if (typeof trigger.MetricName === "undefined") { + + if (typeof trigger.Metrics === 'object') { // This is a CloudWatch metric math alarm. Instead of a MetricName and // statistic there is an array of Metrics where the first element is the - // math expression. - - // first build a dictionary mapping metric ids to metric name and statistic - var sourceMetrics = {}; - for (const metric of trigger.Metrics.slice(1)) { - sourceMetrics[metric.Id] = metric.MetricStat.Stat + ":" - + metric.MetricStat.Metric.MetricName; - } - - // now replace each instance of the metric id in the alarm expression + // math expression. Need to process each metric in the list of Metrics + // and replace occurences of it in the alarm expression alarmExpression = trigger.Metrics[0].Expression; - for (var metricid in sourceMetrics) { - alarmExpression = alarmExpression.replace(new RegExp(metricid,"g"),sourceMetrics[metricid]); + var triggerMetricsLength = trigger.Metrics.length; + for (var i = 1; i < triggerMetricsLength; i++) { + var metric = trigger.Metrics[i]; + alarmExpression = alarmExpression.replace( + new RegExp(escapeStringRegexp(metric.Id), 'g'), + metric.MetricStat.Stat + ':' + metric.MetricStat.Metric.MetricName + ); } } else { // This is a standard CloudWatch alarm on a single metric diff --git a/package-lock.json b/package-lock.json index d7b1ca2..2276e27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -363,6 +363,11 @@ "es6-promise": "^4.0.3" } }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + }, "escodegen": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", diff --git a/package.json b/package.json index 15d9e24..d78f505 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "aws-sdk": "^2.4.0", + "escape-string-regexp": "^2.0.0", "https": "^1.0.0", "lodash": "^4.15.0", "url": "^0.11.0"