Skip to content

Commit d1b1a68

Browse files
Merge pull request #506 from CodeNow/lambda-functions
adding script that is sending autoscaling events to slack
2 parents 21db20d + e81971b commit d1b1a68

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

lambda/asg-events/index.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict'
2+
const https = require('https')
3+
4+
exports.handler = function (event, context) {
5+
let message
6+
try {
7+
message = JSON.parse(event.Records[0].Sns.Message)
8+
} catch (e) {
9+
message = { Description: event.Records[0].Sns.Message }
10+
}
11+
12+
const postData = {
13+
channel: '#aws-notifications',
14+
username: 'AWS',
15+
text: '*' + event.Records[0].Sns.Subject + '*',
16+
icon_emoji: ':cloud:'
17+
}
18+
19+
let slackMessage = [
20+
message.Description,
21+
'',
22+
message.Cause
23+
].join('\n')
24+
25+
if (!message.Description && !message.Cause) {
26+
slackMessage = 'test notification :partyparrot:'
27+
}
28+
29+
postData.attachments = [{
30+
color: 'good',
31+
text: slackMessage
32+
}]
33+
34+
const options = {
35+
method: 'POST',
36+
hostname: 'hooks.slack.com',
37+
port: 443,
38+
path: '/services/T029DEC10/B141U5BHT/UaAwljeWydJaiW6RmD09C4Wx'
39+
}
40+
41+
const req = https.request(options, (res) => {
42+
res.setEncoding('utf8')
43+
res.on('data', () => {})
44+
res.on('end', () => { context.done(null) })
45+
})
46+
47+
req.on('error', (e) => {
48+
console.error('problem with request: ' + e.message)
49+
})
50+
51+
req.write(JSON.stringify(postData))
52+
req.end()
53+
}

lambda/asg-events/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "asg-event-handler",
3+
"version": "1.0.0",
4+
"description": "AWS Lambda Handler for ASG Events.",
5+
"main": "index.js",
6+
"directories": {
7+
"test": "test"
8+
},
9+
"scripts": {
10+
"test": "standard && $npm_package_options_testCommand",
11+
"testonly": "$npm_package_options_testCommand"
12+
},
13+
"options": {
14+
"testCommand": "ava --serial --fail-fast --timeout 5000 --verbose"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/CodeNow/devops-scripts.git"
19+
},
20+
"author": "Bryan Kendall <[email protected]>",
21+
"license": "UNLICENSED",
22+
"bugs": {
23+
"url": "https://github.com/CodeNow/devops-scripts/issues"
24+
},
25+
"homepage": "https://github.com/CodeNow/devops-scripts#readme",
26+
"devDependencies": {
27+
"ava": "^0.15.2",
28+
"sinon": "^1.17.4",
29+
"standard": "^7.1.2"
30+
}
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Records": [
3+
{
4+
"EventSource": "aws:sns",
5+
"EventVersion": "1.0",
6+
"EventSubscriptionArn": "arn:aws:sns:us-west-2:437258487404:asg-events-production-delta:2467eda4-9382-441d-b00f-4baec59c583a",
7+
"Sns": {
8+
"Type": "Notification",
9+
"MessageId": "302ece4d-4377-52fc-87da-81b353f6530d",
10+
"TopicArn": "arn:aws:sns:us-west-2:437258487404:asg-events-production-delta",
11+
"Subject": "Auto Scaling: launch for group \"asg-production-delta-2513635\"",
12+
"Message": "{\"Progress\":50,\"AccountId\":\"437258487404\",\"Description\":\"Launching a new EC2 instance: i-8844d555\",\"RequestId\":\"9d2dc323-d5e1-42a3-9945-515767249fa7\",\"EndTime\":\"2016-07-11T21:23:13.840Z\",\"AutoScalingGroupARN\":\"arn:aws:autoscaling:us-west-2:437258487404:autoScalingGroup:a1b93c0d-4566-420a-a753-4cb9df982cff:autoScalingGroupName/asg-production-delta-2513635\",\"ActivityId\":\"9d2dc323-d5e1-42a3-9945-515767249fa7\",\"StartTime\":\"2016-07-11T21:22:07.785Z\",\"Service\":\"AWS Auto Scaling\",\"Time\":\"2016-07-11T21:23:13.840Z\",\"EC2InstanceId\":\"i-8844d555\",\"StatusCode\":\"InProgress\",\"StatusMessage\":\"\",\"Details\":{\"Subnet ID\":\"subnet-3343206a\",\"Availability Zone\":\"us-west-2c\",\"InvokingAlarms\":[{\"Trigger\":{\"MetricName\":\"Swarm Reserved Memory Maximum Available\",\"ComparisonOperator\":\"LessThanThreshold\",\"Statistic\":\"AVERAGE\",\"Dimensions\":[{\"name\":\"AutoScalingGroupName\",\"value\":\"asg-production-delta-2513635\"}],\"Period\":300,\"EvaluationPeriods\":1,\"Unit\":null,\"Namespace\":\"Runnable/Swarm\",\"Threshold\":2},\"AlarmName\":\"asg-production-delta-2513635-max-available-memory\",\"AlarmDescription\":null,\"AWSAccountId\":\"437258487404\",\"OldStateValue\":\"OK\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (1.7499999999999996) was less than the threshold (2.0).\",\"Region\":\"US West - Oregon\",\"NewStateValue\":\"ALARM\",\"StateChangeTime\":1468272105152}]},\"AutoScalingGroupName\":\"asg-production-delta-2513635\",\"Cause\":\"At 2016-07-11T21:21:45Z a monitor alarm asg-production-delta-2513635-max-available-memory in state ALARM triggered policy scale-out changing the desired capacity from 11 to 12. At 2016-07-11T21:22:06Z an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 11 to 12.\",\"Event\":\"autoscaling:EC2_INSTANCE_LAUNCH\"}",
13+
"Timestamp": "2016-07-11T21:23:13.907Z",
14+
"SignatureVersion": "1",
15+
"Signature": "RBO7jTwwXwNHyW2x4O39FN2Drz7FwvbmfdbUsm+OFFjGXITAkwGms6OFMVYsaGClTXbeycfFcCCCjsSZPeY6o2jZSrfeteYPHUcLs/xSyz1FCaUJN99qxwGYqRVdP/O3wXi65NwRqOTHSK9Ptm9SjRGzCnJfwwLQIjQimmaVaUsVMIGbkVgLPlCJ59qALXXeEJ5zNVVrym3E4b/LsO8D+WEmLySsJQ/92UTBX7ZwQUb/aoEVnqFqgCrCaQQpxztyURjbjl1C4BkXAuC1bHqfJ3czXv/WbuwjLbhvUNK//eutXBaqN+k7aO/VU3wwWQ8Tv5TPQ8qvDaiAbnWUrgUVaA==",
16+
"SigningCertUrl": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",
17+
"UnsubscribeUrl": "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:437258487404:asg-events-production-delta:2467eda4-9382-441d-b00f-4baec59c583a",
18+
"MessageAttributes": {}
19+
}
20+
}
21+
]
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Records": [
3+
{
4+
"EventSource": "aws:sns",
5+
"EventVersion": "1.0",
6+
"EventSubscriptionArn": "arn:aws:sns:us-west-2:437258487404:asg-events-production-delta:2467eda4-9382-441d-b00f-4baec59c583a",
7+
"Sns": {
8+
"Type": "Notification",
9+
"MessageId": "ab6d7f62-fd75-5025-bfe8-309cfbd0f8eb",
10+
"TopicArn": "arn:aws:sns:us-west-2:437258487404:asg-events-production-delta",
11+
"Subject": "Auto Scaling: test notification for group \"asg-production-delta-2685575\"",
12+
"Message": "{\"AccountId\":\"437258487404\",\"RequestId\":\"cc86deb5-47a7-11e6-b529-354c7d92db8f\",\"AutoScalingGroupARN\":\"arn:aws:autoscaling:us-west-2:437258487404:autoScalingGroup:886e6b80-b398-4857-9fe8-16d45b429e2b:autoScalingGroupName/asg-production-delta-2685575\",\"AutoScalingGroupName\":\"asg-production-delta-2685575\",\"Service\":\"AWS Auto Scaling\",\"Event\":\"autoscaling:TEST_NOTIFICATION\",\"Time\":\"2016-07-11T20:41:09.815Z\"}",
13+
"Timestamp": "2016-07-11T20:41:09.950Z",
14+
"SignatureVersion": "1",
15+
"Signature": "V/grO2ol3DgM17q87x+ltyp6C2U5EfEpTeeKI4iwCXsUTfVvARkuRRnAuHTcsAZ4cl2mXQEZXVfJIdXfM6jAYx7RVpQQ+Yn5FpuFR8aBcZmDZWCJxNMVGX/HubQXHI9M33/e2sLPZAD8Bok38o8PyuTS77Wfy//jJMtaJ7KsIToHNxrNERFv1zPiURQFQ3xUA9bo87aaU/8MXha91UWglsyJWxZRKdIc/v2dcRQnPHfOFxAbi5wThOHFoNHmrJupaimKwfjf/QVv9kEfVaz8/vQb6Uz/RxLZPxlgfeOTm8HckyizzZsdmYWANk2rD1xlVHZZAU6TNuXoL9by7yiYWQ==",
16+
"SigningCertUrl": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",
17+
"UnsubscribeUrl": "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:437258487404:asg-events-production-delta:2467eda4-9382-441d-b00f-4baec59c583a",
18+
"MessageAttributes": {}
19+
}
20+
}
21+
]
22+
}

lambda/asg-events/test/index.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const https = require('https')
2+
const EventEmitter = require('events')
3+
4+
class MockRequest extends EventEmitter {
5+
constructor () {
6+
super()
7+
this._written_data = []
8+
}
9+
10+
write (data) {
11+
this._written_data.push(data)
12+
}
13+
14+
end () {
15+
this._ended = true
16+
}
17+
}
18+
19+
class MockResponse extends EventEmitter {
20+
setEncoding (encoding) {
21+
this.encoding = encoding
22+
}
23+
}
24+
25+
const test = require('ava')
26+
const sinon = require('sinon')
27+
const NEW_EC2_INSTANCE = require('./fixtures/new-ec2-instances.json')
28+
const TEST_NOTIFICATION = require('./fixtures/test-notification.json')
29+
30+
const handler = require('../').handler
31+
32+
test.before((t) => {
33+
sinon.stub(https, 'request')
34+
})
35+
36+
test.beforeEach((t) => {
37+
https.request.reset()
38+
})
39+
40+
test.cb('it should call done in the context', (t) => {
41+
const request = new MockRequest()
42+
const response = new MockResponse()
43+
https.request.returns(request)
44+
https.request.yieldsAsync(response)
45+
sinon.stub(request, 'end', () => {
46+
setTimeout(() => { response.emit('end') })
47+
})
48+
handler(NEW_EC2_INSTANCE, {
49+
done: () => {
50+
t.pass()
51+
t.end()
52+
}
53+
})
54+
})
55+
56+
test.cb('it should post to slack', (t) => {
57+
const request = new MockRequest()
58+
const response = new MockResponse()
59+
https.request.returns(request)
60+
https.request.yieldsAsync(response)
61+
sinon.stub(request, 'end', () => {
62+
setTimeout(() => { response.emit('end') })
63+
})
64+
handler(NEW_EC2_INSTANCE, { done: () => {
65+
sinon.assert.calledOnce(https.request)
66+
sinon.assert.calledWithExactly(
67+
https.request,
68+
{
69+
method: 'POST',
70+
hostname: 'hooks.slack.com',
71+
port: 443,
72+
path: sinon.match(/\/services\/[^\/]+\/[^\/]+\/[^\/]+/)
73+
},
74+
sinon.match.func
75+
)
76+
t.end()
77+
} })
78+
})
79+
80+
test.cb('it should send the description and cause', (t) => {
81+
const request = new MockRequest()
82+
const response = new MockResponse()
83+
https.request.returns(request)
84+
https.request.yieldsAsync(response)
85+
sinon.stub(request, 'end', () => {
86+
setTimeout(() => { response.emit('end') })
87+
})
88+
handler(NEW_EC2_INSTANCE, { done: () => {
89+
t.is(request._written_data.length, 1, 'one write')
90+
const s = JSON.parse(request._written_data.pop())
91+
t.regex(s.attachments[0].text, /Launching a new EC2 instance/)
92+
t.end()
93+
} })
94+
})
95+
96+
test.cb('it should send a test message', (t) => {
97+
const request = new MockRequest()
98+
const response = new MockResponse()
99+
https.request.returns(request)
100+
https.request.yieldsAsync(response)
101+
sinon.stub(request, 'end', () => {
102+
setTimeout(() => { response.emit('end') })
103+
})
104+
handler(TEST_NOTIFICATION, { done: () => {
105+
t.is(request._written_data.length, 1, 'one write')
106+
const s = JSON.parse(request._written_data.pop())
107+
t.regex(s.attachments[0].text, /test notification :partyparrot:/)
108+
t.end()
109+
} })
110+
})

0 commit comments

Comments
 (0)