Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
30 changes: 25 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If trigger.MetricName and trigger.Metrics are mutually exclusive, it may improve readability to say "If this trigger has multiple metrics, use them" instead of "If this trigger doesn't have a single metric, use multiple." if (typeof trigger.Metrics === 'object') will determine if trigger.Metrics is an array.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also Array.isArray(trigger.Metrics) instead of checking typeof. While it is a part of the ES5 spec, older browser has spotty implementations. NodeJS should not have a problem with it. If you find that ES6 is acceptable for this file, Array.isArray should be acceptable as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to 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)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const and for...of loops are ES6, but the rest of this file seems to be using ES5 (e.g. var instead of let or const). I'd make sure ES6 is supported and that ES5 is not otherwise a requirement. It is odd to see both in one file.

As ES5, this would be:

var triggerMetricsLength = trigger.Metrics.length;
for (var i = 1; i < triggerMetricsLength; i++) {
  var metric = trigger.Metrics[i];
  // ...
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combined this change with below recommendation

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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for...in loops should be avoided. If you are just trying to get the metric.Ids, this loop can probably be a copy of or merged with the previous.

alarmExpression = trigger.Metrics[0].Expression;
var triggerMetricsLength = trigger.Metrics.length;
for (var i = 1; i < triggerMetricsLength; i++) {
  var metric = trigger.Metrics[i];
  alarmExpression = alarmExpression.replace(
    new RegExp(metric.Id, 'g'),
    metric.MetricStat.Stat + ':' + metric.MetricStat.Metric.MetricName
  );
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great suggestion - adopted this logic

alarmExpression = alarmExpression.replace(new RegExp(metricid,"g"),sourceMetrics[metricid]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If metric.Id is not intended to be a regular expression itself, it should probably be escaped. As is, a malicious user can submit a custom, unsanitized regular expression as input, which may produce undesirable effects if we parse it.

function sanitizeStringForRegExp(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
alarmExpression = alarmExpression.replace(
  new RegExp(sanitizeStringForRegExp(metricid), 'g'),
  sourceMetrics[metricid]
);

Source: Stack Overflow

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorporated escape-string-regexp package which implements the equivalent of sanitizeStringForRegExp above.

}
} 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") {
Expand All @@ -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 "
Expand Down Expand Up @@ -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){
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "lambda-cloudwatch-slack",
"version": "0.3.0",
"description": "Better Slack notifications for AWS CloudWatch",
"authors": [
"authors": [
"Christopher Reichert <creichert07@gmail.com>",
"Cody Reichert <codyreichert@gmail.com>",
"Alexandr Promakh <s-promakh@ya.ru>"
Expand Down
3 changes: 2 additions & 1 deletion scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
$NODE_LAMBDA run -x test/context.json -j test/sns-autoscaling-event.json
18 changes: 18 additions & 0 deletions test/sns-cloudwatch-event-metricmath.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
}
]
}