diff --git a/config.js b/config.js index ce51bc7..7c98ea9 100644 --- a/config.js +++ b/config.js @@ -1,30 +1,32 @@ module.exports = { kmsEncryptedHookUrl: process.env.KMS_ENCRYPTED_HOOK_URL, // encrypted slack webhook url - unencryptedHookUrl: process.env.UNENCRYPTED_HOOK_URL, // unencrypted slack webhook url + unencryptedHookUrl: process.env.UNENCRYPTED_HOOK_URL, // unencrypted slack webhook url services: { elasticbeanstalk: { // text in the sns message or topicname to match on to process this service type - match_text: "ElasticBeanstalkNotifications" - }, - cloudwatch: { + match_text: 'ElasticBeanstalkNotifications' }, + cloudwatch: {}, codepipeline: { // text in the sns message or topicname to match on to process this service type - match_text: "CodePipelineNotifications" + match_text: 'CodePipelineNotifications' }, codedeploy: { // text in the sns message or topicname to match on to process this service type - match_text: "CodeDeploy" + match_text: 'CodeDeploy' }, elasticache: { // text in the sns message or topicname to match on to process this service type - match_text: "ElastiCache" + match_text: 'ElastiCache' }, autoscaling: { // text in the sns message or topicname to match on to process this service type - match_text: "AutoScaling" + match_text: 'AutoScaling' + }, + glue: { + // text in the sns message or topicname to match on to process this service type + match_text: 'Glue' } } - -} \ No newline at end of file +} diff --git a/index.js b/index.js index c61ab4e..8f33196 100644 --- a/index.js +++ b/index.js @@ -1,436 +1,533 @@ -var AWS = require('aws-sdk'); -var url = require('url'); -var https = require('https'); -var config = require('./config'); -var _ = require('lodash'); -var hookUrl; +var AWS = require('aws-sdk') +var url = require('url') +var https = require('https') +var config = require('./config') +var _ = require('lodash') +var hookUrl var baseSlackMessage = {} -var postMessage = function(message, callback) { - var body = JSON.stringify(message); - var options = url.parse(hookUrl); - options.method = 'POST'; +var postMessage = function (message, callback) { + var body = JSON.stringify(message) + var options = url.parse(hookUrl) + options.method = 'POST' options.headers = { 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(body), - }; - - var postReq = https.request(options, function(res) { - var chunks = []; - res.setEncoding('utf8'); - res.on('data', function(chunk) { - return chunks.push(chunk); - }); - res.on('end', function() { - var body = chunks.join(''); + 'Content-Length': Buffer.byteLength(body) + } + + var postReq = https.request(options, function (res) { + var chunks = [] + res.setEncoding('utf8') + res.on('data', function (chunk) { + return chunks.push(chunk) + }) + res.on('end', function () { + var body = chunks.join('') if (callback) { callback({ body: body, statusCode: res.statusCode, statusMessage: res.statusMessage - }); + }) } - }); - return res; - }); - - postReq.write(body); - postReq.end(); -}; - -var handleElasticBeanstalk = function(event, context) { - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var subject = event.Records[0].Sns.Subject || "AWS Elastic Beanstalk Notification"; - var message = event.Records[0].Sns.Message; - - var stateRed = message.indexOf(" to RED"); - var stateSevere = message.indexOf(" to Severe"); - var butWithErrors = message.indexOf(" but with errors"); - var noPermission = message.indexOf("You do not have permission"); - var failedDeploy = message.indexOf("Failed to deploy application"); - var failedConfig = message.indexOf("Failed to deploy configuration"); - var failedQuota = message.indexOf("Your quota allows for 0 more running instance"); - var unsuccessfulCommand = message.indexOf("Unsuccessful command execution"); - - var stateYellow = message.indexOf(" to YELLOW"); - var stateDegraded = message.indexOf(" to Degraded"); - var stateInfo = message.indexOf(" to Info"); - var removedInstance = message.indexOf("Removed instance "); - var addingInstance = message.indexOf("Adding instance "); - var abortedOperation = message.indexOf(" aborted operation."); - var abortedDeployment = message.indexOf("some instances may have deployed the new application version"); - - var color = "good"; - - if (stateRed != -1 || stateSevere != -1 || butWithErrors != -1 || noPermission != -1 || failedDeploy != -1 || failedConfig != -1 || failedQuota != -1 || unsuccessfulCommand != -1) { - color = "danger"; + }) + return res + }) + + postReq.write(body) + postReq.end() +} + +var handleElasticBeanstalk = function (event, context) { + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var subject = event.Records[0].Sns.Subject || 'AWS Elastic Beanstalk Notification' + var message = event.Records[0].Sns.Message + + var stateRed = message.indexOf(' to RED') + var stateSevere = message.indexOf(' to Severe') + var butWithErrors = message.indexOf(' but with errors') + var noPermission = message.indexOf('You do not have permission') + var failedDeploy = message.indexOf('Failed to deploy application') + var failedConfig = message.indexOf('Failed to deploy configuration') + var failedQuota = message.indexOf('Your quota allows for 0 more running instance') + var unsuccessfulCommand = message.indexOf('Unsuccessful command execution') + + var stateYellow = message.indexOf(' to YELLOW') + var stateDegraded = message.indexOf(' to Degraded') + var stateInfo = message.indexOf(' to Info') + var removedInstance = message.indexOf('Removed instance ') + var addingInstance = message.indexOf('Adding instance ') + var abortedOperation = message.indexOf(' aborted operation.') + var abortedDeployment = message.indexOf('some instances may have deployed the new application version') + + var color = 'good' + + if ( + stateRed != -1 || + stateSevere != -1 || + butWithErrors != -1 || + noPermission != -1 || + failedDeploy != -1 || + failedConfig != -1 || + failedQuota != -1 || + unsuccessfulCommand != -1 + ) { + color = 'danger' } - if (stateYellow != -1 || stateDegraded != -1 || stateInfo != -1 || removedInstance != -1 || addingInstance != -1 || abortedOperation != -1 || abortedDeployment != -1) { - color = "warning"; + if ( + stateYellow != -1 || + stateDegraded != -1 || + stateInfo != -1 || + removedInstance != -1 || + addingInstance != -1 || + abortedOperation != -1 || + abortedDeployment != -1 + ) { + color = 'warning' } var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "fields": [ - { "title": "Subject", "value": event.Records[0].Sns.Subject, "short": false}, - { "title": "Message", "value": message, "short": false} + fields: [ + { title: 'Subject', value: event.Records[0].Sns.Subject, short: false }, + { title: 'Message', value: message, short: false } ], - "color": color, - "ts": timestamp + color: color, + ts: timestamp } ] - }; + } - return _.merge(slackMessage, baseSlackMessage); -}; + return _.merge(slackMessage, baseSlackMessage) +} -var handleCodeDeploy = function(event, context) { - var subject = "AWS CodeDeploy Notification"; - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var snsSubject = event.Records[0].Sns.Subject; - var message; - var fields = []; - var color = "warning"; +var handleCodeDeploy = function (event, context) { + var subject = 'AWS CodeDeploy Notification' + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var snsSubject = event.Records[0].Sns.Subject + var message + var fields = [] + var color = 'warning' try { - message = JSON.parse(event.Records[0].Sns.Message); + message = JSON.parse(event.Records[0].Sns.Message) - if(message.status === "SUCCEEDED"){ - color = "good"; - } else if(message.status === "FAILED"){ - color = "danger"; + if (message.status === 'SUCCEEDED') { + color = 'good' + } else if (message.status === 'FAILED') { + color = 'danger' } - fields.push({ "title": "Message", "value": snsSubject, "short": false }); - fields.push({ "title": "Deployment Group", "value": message.deploymentGroupName, "short": true }); - fields.push({ "title": "Application", "value": message.applicationName, "short": true }); + fields.push({ title: 'Message', value: snsSubject, short: false }) + fields.push({ title: 'Deployment Group', value: message.deploymentGroupName, short: true }) + fields.push({ title: 'Application', value: message.applicationName, short: true }) fields.push({ - "title": "Status Link", - "value": "https://console.aws.amazon.com/codedeploy/home?region=" + message.region + "#/deployments/" + message.deploymentId, - "short": false - }); - } - catch(e) { - color = "good"; - message = event.Records[0].Sns.Message; - fields.push({ "title": "Message", "value": snsSubject, "short": false }); - fields.push({ "title": "Detail", "value": message, "short": false }); + title: 'Status Link', + value: + 'https://console.aws.amazon.com/codedeploy/home?region=' + + message.region + + '#/deployments/' + + message.deploymentId, + short: false + }) + } catch (e) { + color = 'good' + message = event.Records[0].Sns.Message + fields.push({ title: 'Message', value: snsSubject, short: false }) + fields.push({ title: 'Detail', value: message, short: false }) } - var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "color": color, - "fields": fields, - "ts": timestamp + color: color, + fields: fields, + ts: timestamp } ] - }; + } - return _.merge(slackMessage, baseSlackMessage); -}; + return _.merge(slackMessage, baseSlackMessage) +} -var handleCodePipeline = function(event, context) { - var subject = "AWS CodePipeline Notification"; - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var snsSubject = event.Records[0].Sns.Subject; - var message; - var fields = []; - var color = "warning"; - var changeType = ""; +var handleCodePipeline = function (event, context) { + var subject = 'AWS CodePipeline Notification' + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var snsSubject = event.Records[0].Sns.Subject + var message + var fields = [] + var color = 'warning' + var changeType = '' try { - message = JSON.parse(event.Records[0].Sns.Message); - detailType = message['detail-type']; - - if(detailType === "CodePipeline Pipeline Execution State Change"){ - changeType = ""; - } else if(detailType === "CodePipeline Stage Execution State Change"){ - changeType = "STAGE " + message.detail.stage; - } else if(detailType === "CodePipeline Action Execution State Change"){ - changeType = "ACTION"; + message = JSON.parse(event.Records[0].Sns.Message) + detailType = message['detail-type'] + + if (detailType === 'CodePipeline Pipeline Execution State Change') { + changeType = '' + } else if (detailType === 'CodePipeline Stage Execution State Change') { + changeType = 'STAGE ' + message.detail.stage + } else if (detailType === 'CodePipeline Action Execution State Change') { + changeType = 'ACTION' } - if(message.detail.state === "SUCCEEDED"){ - color = "good"; - } else if(message.detail.state === "FAILED"){ - color = "danger"; + if (message.detail.state === 'SUCCEEDED') { + color = 'good' + } else if (message.detail.state === 'FAILED') { + color = 'danger' } - header = message.detail.state + ": CodePipeline " + changeType; - fields.push({ "title": "Message", "value": header, "short": false }); - fields.push({ "title": "Pipeline", "value": message.detail.pipeline, "short": true }); - fields.push({ "title": "Region", "value": message.region, "short": true }); + header = message.detail.state + ': CodePipeline ' + changeType + fields.push({ title: 'Message', value: header, short: false }) + fields.push({ title: 'Pipeline', value: message.detail.pipeline, short: true }) + fields.push({ title: 'Region', value: message.region, short: true }) fields.push({ - "title": "Status Link", - "value": "https://console.aws.amazon.com/codepipeline/home?region=" + message.region + "#/view/" + message.detail.pipeline, - "short": false - }); - } - catch(e) { - color = "good"; - message = event.Records[0].Sns.Message; - header = message.detail.state + ": CodePipeline " + message.detail.pipeline; - fields.push({ "title": "Message", "value": header, "short": false }); - fields.push({ "title": "Detail", "value": message, "short": false }); + title: 'Status Link', + value: + 'https://console.aws.amazon.com/codepipeline/home?region=' + + message.region + + '#/view/' + + message.detail.pipeline, + short: false + }) + } catch (e) { + color = 'good' + message = event.Records[0].Sns.Message + header = message.detail.state + ': CodePipeline ' + message.detail.pipeline + fields.push({ title: 'Message', value: header, short: false }) + fields.push({ title: 'Detail', value: message, short: false }) } - var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "color": color, - "fields": fields, - "ts": timestamp + color: color, + fields: fields, + ts: timestamp } ] - }; - - return _.merge(slackMessage, baseSlackMessage); -}; - -var handleElasticache = function(event, context) { - var subject = "AWS ElastiCache Notification" - var message = JSON.parse(event.Records[0].Sns.Message); - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var region = event.Records[0].EventSubscriptionArn.split(":")[3]; - var eventname, nodename; - var color = "good"; - - for(key in message){ - eventname = key; - nodename = message[key]; - break; + } + + return _.merge(slackMessage, baseSlackMessage) +} + +var handleElasticache = function (event, context) { + var subject = 'AWS ElastiCache Notification' + var message = JSON.parse(event.Records[0].Sns.Message) + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var region = event.Records[0].EventSubscriptionArn.split(':')[3] + var eventname, nodename + var color = 'good' + + for (key in message) { + eventname = key + nodename = message[key] + break } var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "color": color, - "fields": [ - { "title": "Event", "value": eventname.split(":")[1], "short": true }, - { "title": "Node", "value": nodename, "short": true }, + color: color, + fields: [ + { title: 'Event', value: eventname.split(':')[1], short: true }, + { title: 'Node', value: nodename, short: true }, { - "title": "Link to cache node", - "value": "https://console.aws.amazon.com/elasticache/home?region=" + region + "#cache-nodes:id=" + nodename + ";nodes", - "short": false + title: 'Link to cache node', + value: + 'https://console.aws.amazon.com/elasticache/home?region=' + + region + + '#cache-nodes:id=' + + nodename + + ';nodes', + short: false } ], - "ts": timestamp + ts: timestamp } ] - }; - return _.merge(slackMessage, baseSlackMessage); -}; - -var handleCloudWatch = function(event, context) { - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var message = JSON.parse(event.Records[0].Sns.Message); - var region = event.Records[0].EventSubscriptionArn.split(":")[3]; - var subject = "AWS CloudWatch Notification"; - var alarmName = message.AlarmName; - var metricName = message.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") { - color = "danger"; - } else if (message.NewStateValue === "OK") { - color = "good"; + } + return _.merge(slackMessage, baseSlackMessage) +} + +var handleCloudWatch = function (event, context) { + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var message = JSON.parse(event.Records[0].Sns.Message) + var region = event.Records[0].EventSubscriptionArn.split(':')[3] + var subject = 'AWS CloudWatch Notification' + var alarmName = message.AlarmName + var metricName = message.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') { + color = 'danger' + } else if (message.NewStateValue === 'OK') { + color = 'good' } var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "color": color, - "fields": [ - { "title": "Alarm Name", "value": alarmName, "short": true }, - { "title": "Alarm Description", "value": alarmDescription, "short": false}, + color: color, + fields: [ + { title: 'Alarm Name', value: alarmName, short: true }, + { title: 'Alarm Description', value: alarmDescription, short: false }, { - "title": "Trigger", - "value": trigger.Statistic + " " - + metricName + " " - + trigger.ComparisonOperator + " " - + trigger.Threshold + " for " - + trigger.EvaluationPeriods + " period(s) of " - + trigger.Period + " seconds.", - "short": false + title: 'Trigger', + value: + trigger.Statistic + + ' ' + + metricName + + ' ' + + trigger.ComparisonOperator + + ' ' + + trigger.Threshold + + ' for ' + + trigger.EvaluationPeriods + + ' period(s) of ' + + trigger.Period + + ' seconds.', + short: false }, - { "title": "Old State", "value": oldState, "short": true }, - { "title": "Current State", "value": newState, "short": true }, + { title: 'Old State', value: oldState, short: true }, + { title: 'Current State', value: newState, short: true }, { - "title": "Link to Alarm", - "value": "https://console.aws.amazon.com/cloudwatch/home?region=" + region + "#alarm:alarmFilter=ANY;name=" + encodeURIComponent(alarmName), - "short": false + title: 'Link to Alarm', + value: + 'https://console.aws.amazon.com/cloudwatch/home?region=' + + region + + '#alarm:alarmFilter=ANY;name=' + + encodeURIComponent(alarmName), + short: false } ], - "ts": timestamp + ts: timestamp } ] - }; - return _.merge(slackMessage, baseSlackMessage); -}; - -var handleAutoScaling = function(event, context) { - var subject = "AWS AutoScaling Notification" - var message = JSON.parse(event.Records[0].Sns.Message); - var timestamp = (new Date(event.Records[0].Sns.Timestamp)).getTime()/1000; - var eventname, nodename; - var color = "good"; - - for(key in message){ - eventname = key; - nodename = message[key]; - break; + } + return _.merge(slackMessage, baseSlackMessage) +} + +var handleAutoScaling = function (event, context) { + var subject = 'AWS AutoScaling Notification' + var message = JSON.parse(event.Records[0].Sns.Message) + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var eventname, nodename + var color = 'good' + + for (key in message) { + eventname = key + nodename = message[key] + break } var slackMessage = { - text: "*" + subject + "*", + text: '*' + subject + '*', attachments: [ { - "color": color, - "fields": [ - { "title": "Message", "value": event.Records[0].Sns.Subject, "short": false }, - { "title": "Description", "value": message.Description, "short": false }, - { "title": "Event", "value": message.Event, "short": false }, - { "title": "Cause", "value": message.Cause, "short": false } - + color: color, + fields: [ + { title: 'Message', value: event.Records[0].Sns.Subject, short: false }, + { title: 'Description', value: message.Description, short: false }, + { title: 'Event', value: message.Event, short: false }, + { title: 'Cause', value: message.Cause, short: false } ], - "ts": timestamp + ts: timestamp } ] - }; - return _.merge(slackMessage, baseSlackMessage); -}; - -var handleCatchAll = function(event, context) { - - var record = event.Records[0] - var subject = record.Sns.Subject - var timestamp = new Date(record.Sns.Timestamp).getTime() / 1000; - var message = JSON.parse(record.Sns.Message) - var color = "warning"; - - if (message.NewStateValue === "ALARM") { - color = "danger"; - } else if (message.NewStateValue === "OK") { - color = "good"; - } + } + return _.merge(slackMessage, baseSlackMessage) +} - // Add all of the values from the event message to the Slack message description - var description = "" - for(key in message) { +var handleGlue = function (event, context) { + var subject = 'AWS Glue Notification' + var timestamp = new Date(event.Records[0].Sns.Timestamp).getTime() / 1000 + var message + var fields = [] + var color = 'warning' - var renderedMessage = typeof message[key] === 'object' - ? JSON.stringify(message[key]) - : message[key] + try { + message = JSON.parse(event.Records[0].Sns.Message) - description = description + "\n" + key + ": " + renderedMessage + if (message.detail.state === 'SUCCEEDED') { + color = 'good' + } else if (message.detail.state === 'FAILED') { + color = 'danger' } + header = message.detail.state + ': Glue' + fields.push({ title: 'Message', value: header, short: true }) + fields.push({ title: 'Severity', value: message.detail.severity, short: true }) + fields.push({ title: 'Job Name', value: message.detail.jobName, short: false }) + fields.push({ title: 'Job Run Id', value: message.detail.jobRunId, short: false }) + fields.push({ title: 'Region', value: message.region, short: true }) + fields.push({ title: 'Details', value: message.detail.message, short: false }) + } catch (e) { + color = 'warning' + message = event.Records[0].Sns.Message + header = message.detail.state + ': Glue ' + fields.push({ title: 'Message', value: header, short: false }) + fields.push({ title: 'Detail', value: message, short: false }) + } - var slackMessage = { - text: "*" + subject + "*", - attachments: [ - { - "color": color, - "fields": [ - { "title": "Message", "value": record.Sns.Subject, "short": false }, - { "title": "Description", "value": description, "short": false } - ], - "ts": timestamp - } - ] - } + var slackMessage = { + text: '*' + subject + '*', + attachments: [ + { + color: color, + fields: fields, + ts: timestamp + } + ] + } - return _.merge(slackMessage, baseSlackMessage); -} + console.log(slackMessage) -var processEvent = function(event, context) { - console.log("sns received:" + JSON.stringify(event, null, 2)); - var slackMessage = null; - var eventSubscriptionArn = event.Records[0].EventSubscriptionArn; - var eventSnsSubject = event.Records[0].Sns.Subject || 'no subject'; - var eventSnsMessageRaw = event.Records[0].Sns.Message; - var eventSnsMessage = null; + return _.merge(slackMessage, baseSlackMessage) +} - try { - eventSnsMessage = JSON.parse(eventSnsMessageRaw); - } - catch (e) { +var handleCatchAll = function (event, context) { + var record = event.Records[0] + var subject = record.Sns.Subject + var timestamp = new Date(record.Sns.Timestamp).getTime() / 1000 + var message = JSON.parse(record.Sns.Message) + var color = 'warning' + + if (message.NewStateValue === 'ALARM') { + color = 'danger' + } else if (message.NewStateValue === 'OK') { + color = 'good' } - 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){ - console.log("processing codepipeline notification"); - slackMessage = handleCodePipeline(event,context) - } - else if(eventSubscriptionArn.indexOf(config.services.elasticbeanstalk.match_text) > -1 || eventSnsSubject.indexOf(config.services.elasticbeanstalk.match_text) > -1 || eventSnsMessageRaw.indexOf(config.services.elasticbeanstalk.match_text) > -1){ - console.log("processing elasticbeanstalk notification"); - slackMessage = handleElasticBeanstalk(event,context) - } - else if(eventSnsMessage && 'AlarmName' in eventSnsMessage && 'AlarmDescription' in eventSnsMessage){ - console.log("processing cloudwatch notification"); - slackMessage = handleCloudWatch(event,context); - } - else if(eventSubscriptionArn.indexOf(config.services.codedeploy.match_text) > -1 || eventSnsSubject.indexOf(config.services.codedeploy.match_text) > -1 || eventSnsMessageRaw.indexOf(config.services.codedeploy.match_text) > -1){ - console.log("processing codedeploy notification"); - slackMessage = handleCodeDeploy(event,context); - } - else if(eventSubscriptionArn.indexOf(config.services.elasticache.match_text) > -1 || eventSnsSubject.indexOf(config.services.elasticache.match_text) > -1 || eventSnsMessageRaw.indexOf(config.services.elasticache.match_text) > -1){ - console.log("processing elasticache notification"); - slackMessage = handleElasticache(event,context); + // Add all of the values from the event message to the Slack message description + var description = '' + for (key in message) { + var renderedMessage = typeof message[key] === 'object' ? JSON.stringify(message[key]) : message[key] + + description = description + '\n' + key + ': ' + renderedMessage } - else if(eventSubscriptionArn.indexOf(config.services.autoscaling.match_text) > -1 || eventSnsSubject.indexOf(config.services.autoscaling.match_text) > -1 || eventSnsMessageRaw.indexOf(config.services.autoscaling.match_text) > -1){ - console.log("processing autoscaling notification"); - slackMessage = handleAutoScaling(event, context); + + var slackMessage = { + text: '*' + subject + '*', + attachments: [ + { + color: color, + fields: [ + { title: 'Message', value: record.Sns.Subject, short: false }, + { title: 'Description', value: description, short: false } + ], + ts: timestamp + } + ] } - else{ - slackMessage = handleCatchAll(event, context); + + return _.merge(slackMessage, baseSlackMessage) +} + +var processEvent = function (event, context) { + console.log('sns received:' + JSON.stringify(event, null, 2)) + var slackMessage = null + var eventSubscriptionArn = event.Records[0].EventSubscriptionArn + var eventSnsSubject = event.Records[0].Sns.Subject || 'no subject' + var eventSnsMessageRaw = event.Records[0].Sns.Message + var eventSnsMessage = null + + try { + eventSnsMessage = JSON.parse(eventSnsMessageRaw) + } 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 + ) { + console.log('processing codepipeline notification') + slackMessage = handleCodePipeline(event, context) + } else if ( + eventSubscriptionArn.indexOf(config.services.elasticbeanstalk.match_text) > -1 || + eventSnsSubject.indexOf(config.services.elasticbeanstalk.match_text) > -1 || + eventSnsMessageRaw.indexOf(config.services.elasticbeanstalk.match_text) > -1 + ) { + console.log('processing elasticbeanstalk notification') + slackMessage = handleElasticBeanstalk(event, context) + } else if (eventSnsMessage && 'AlarmName' in eventSnsMessage && 'AlarmDescription' in eventSnsMessage) { + console.log('processing cloudwatch notification') + slackMessage = handleCloudWatch(event, context) + } else if ( + eventSubscriptionArn.indexOf(config.services.codedeploy.match_text) > -1 || + eventSnsSubject.indexOf(config.services.codedeploy.match_text) > -1 || + eventSnsMessageRaw.indexOf(config.services.codedeploy.match_text) > -1 + ) { + console.log('processing codedeploy notification') + slackMessage = handleCodeDeploy(event, context) + } else if ( + eventSubscriptionArn.indexOf(config.services.elasticache.match_text) > -1 || + eventSnsSubject.indexOf(config.services.elasticache.match_text) > -1 || + eventSnsMessageRaw.indexOf(config.services.elasticache.match_text) > -1 + ) { + console.log('processing elasticache notification') + slackMessage = handleElasticache(event, context) + } else if ( + eventSubscriptionArn.indexOf(config.services.autoscaling.match_text) > -1 || + eventSnsSubject.indexOf(config.services.autoscaling.match_text) > -1 || + eventSnsMessageRaw.indexOf(config.services.autoscaling.match_text) > -1 + ) { + console.log('processing autoscaling notification') + slackMessage = handleAutoScaling(event, context) + } else if ( + eventSubscriptionArn.indexOf(config.services.glue.match_text) > -1 || + eventSnsSubject.indexOf(config.services.glue.match_text) > -1 || + eventSnsMessageRaw.indexOf(config.services.glue.match_text) > -1 + ) { + console.log('processing glue notification') + slackMessage = handleGlue(event, context) + } else { + slackMessage = handleCatchAll(event, context) } - postMessage(slackMessage, function(response) { + postMessage(slackMessage, function (response) { if (response.statusCode < 400) { - console.info('message posted successfully'); - context.succeed(); + console.info('message posted successfully') + context.succeed() } else if (response.statusCode < 500) { - console.error("error posting message to slack API: " + response.statusCode + " - " + response.statusMessage); + console.error('error posting message to slack API: ' + response.statusCode + ' - ' + response.statusMessage) // Don't retry because the error is due to a problem with the request - context.succeed(); + context.succeed() } else { // Let Lambda retry - context.fail("server error when processing message: " + response.statusCode + " - " + response.statusMessage); + context.fail('server error when processing message: ' + response.statusCode + ' - ' + response.statusMessage) } - }); -}; + }) +} -exports.handler = function(event, context) { +exports.handler = function (event, context) { if (hookUrl) { - processEvent(event, context); + processEvent(event, context) } else if (config.unencryptedHookUrl) { - hookUrl = config.unencryptedHookUrl; - processEvent(event, context); + hookUrl = config.unencryptedHookUrl + processEvent(event, context) } else if (config.kmsEncryptedHookUrl && config.kmsEncryptedHookUrl !== '') { - var encryptedBuf = new Buffer(config.kmsEncryptedHookUrl, 'base64'); - var cipherText = { CiphertextBlob: encryptedBuf }; - var kms = new AWS.KMS(); + var encryptedBuf = new Buffer(config.kmsEncryptedHookUrl, 'base64') + var cipherText = { CiphertextBlob: encryptedBuf } + var kms = new AWS.KMS() - kms.decrypt(cipherText, function(err, data) { + kms.decrypt(cipherText, function (err, data) { if (err) { - console.log("decrypt error: " + err); - processEvent(event, context); + console.log('decrypt error: ' + err) + processEvent(event, context) } else { - hookUrl = "https://" + data.Plaintext.toString('ascii'); - processEvent(event, context); + hookUrl = 'https://' + data.Plaintext.toString('ascii') + processEvent(event, context) } - }); + }) } else { - context.fail('hook url has not been set.'); + context.fail('hook url has not been set.') } -}; +} diff --git a/test/sns-glue-event.json b/test/sns-glue-event.json new file mode 100644 index 0000000..4f88d3c --- /dev/null +++ b/test/sns-glue-event.json @@ -0,0 +1,18 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:us-west-2:123456789123:GlueNotifications:00000000-0000-0000-0000-000000000000", + "Sns": { + "Type": "Notification", + "MessageId": "00000000-0000-0000-0000-000000000000", + "TopicArn": "arn:aws:sns:us-west-2:123456789123:GlueNotifications", + "Timestamp": "2016-08-11T07:24:05.959Z", + "Subject": "Glue Job State Change", + "Message": "{\"version\":\"0\",\"id\":\"226542f8-c37d-02bb-6ca0-990a964ccfbc\",\"detail-type\":\"Glue Job State Change\",\"source\":\"aws.glue\",\"account\":\"478058018293\",\"time\":\"2021-01-04T04:13:09Z\",\"region\":\"ap-southeast-2\",\"resources\":[],\"detail\":{\"jobName\":\"metrics-data-etl-job-v2-ap-southeast-2-dev-apps\",\"severity\":\"ERROR\",\"state\":\"FAILED\",\"jobRunId\":\"jr_895cdcd39fdfee4f1ed1010ad58810502efcde1217be3a58f02e3ed32858440c\",\"message\":\"JobName:metrics-data-etl-job-v2-ap-southeast-2-dev-apps and JobRunId:jr_895cdcd39fdfee4f1ed1010ad58810502efcde1217be3a58f02e3ed32858440c failed to execute with exception Role arn:aws:iam::478058018293:role\/metrics-data-glue-etl-v2-ap-southeast-2-dev-apps should be given assume role permissions for Glue Service.\\n\"}}", + "MessageAttributes": {} + } + } + ] +}