forked from hubot-archive/hubot-pagerduty-github
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpagerduty-github.coffee
485 lines (412 loc) · 17.6 KB
/
pagerduty-github.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# Description:
# PagerDuty Integration for checking who's on call, making exceptions, ack, resolve, etc.
#
# Commands:
#
# hubot who's on call - return the username of who's on call
# hubot pager me trigger <msg> - create a new incident with <msg>
# hubot pager me 60 - take the pager for 60 minutes
# hubot pager me as <email> - remember your pager email is <email>
# hubot pager me incidents - return the current incidents
# hubot pager me incident NNN - return the incident NNN
# hubot pager me note <incident> <content> - add note to incident #<incident> with <content>
# hubot pager me notes <incident> - show notes for incident #<incident>
# hubot pager me problems - return all open incidents
# hubot pager me ack <incident> - ack incident #<incident>
# hubot pager me resolve <incident1> <incident2> ... <incidentN> - ack all specified incidents
# hubot pager me ack - ack all triggered incidents
# hubot pager me resolve <incident> - resolve incident #<incident>
# hubot pager me resolve <incident1> <incident2> ... <incidentN>- resolve all specified incidents
# hubot pager me resolve - resolve all acknowledged incidents
#
# Dependencies:
# "moment": "1.6.2"
#
# Notes:
# To setup the webhooks and get the alerts in your chatrooms, you need to add the endpoint you define here (e.g /hooks) in
# the service settings of your Pagerduty accounts. You also need to define the room in which you want them to appear.
# (Unless you want to spam all the rooms with alerts, but we don't believe that should be the default behavior :)
#
# URLS:
# http://developer.pagerduty.com/documentation/rest/webhooks
# http://support.pagerduty.com/entries/21774694-Webhooks-
#
# Configuration:
#
# HUBOT_PAGERDUTY_API_KEY - API Access Key
# HUBOT_PAGERDUTY_SUBDOMAIN
# HUBOT_PAGERDUTY_SERVICE_API_KEY - Service API Key from a 'General API Service'
# HUBOT_PAGERDUTY_SCHEDULE_ID
# HUBOT_PAGERDUTY_ROOM - Room in which you want the pagerduty webhook notifications to appear
# HUBOT_PAGERDUTY_ENDPOINT - Pagerduty Webhook listener e.g /hook
#
# Authors:
# Jesse Newland, Josh Nicols, Jacob Bednarz, Chris Lundquist, Chris Streeter, Joseph Pierri, Greg Hoin
#
inspect = require('util').inspect
moment = require('moment')
pagerDutyApiKey = process.env.HUBOT_PAGERDUTY_API_KEY
pagerDutySubdomain = process.env.HUBOT_PAGERDUTY_SUBDOMAIN
pagerDutyBaseUrl = "https://#{pagerDutySubdomain}.pagerduty.com/api/v1"
pagerDutyServiceApiKey = process.env.HUBOT_PAGERDUTY_SERVICE_API_KEY
pagerDutyScheduleId = process.env.HUBOT_PAGERDUTY_SCHEDULE_ID
pagerRoom = process.env.HUBOT_PAGERDUTY_ROOM
# Webhook listener endpoint. Set it to whatever URL you want, and make sure it matches your pagerduty service settings
pagerEndpoint = process.env.HUBOT_PAGERDUTY_ENDPOINT || "/hook"
module.exports = (robot) ->
robot.respond /pager( me)?$/i, (msg) ->
if missingEnvironmentForApi(msg)
return
withPagerDutyUser msg, (user) ->
emailNote = if msg.message.user.pagerdutyEmail
"You've told me your PagerDuty email is #{msg.message.user.pagerdutyEmail}"
else if msg.message.user.email_address
"I'm assuming your PagerDuty email is #{msg.message.user.email_address}. Change it with `#{robot.name} pager me as [email protected]`"
if user
msg.send "I found your PagerDuty user https://#{pagerDutySubdomain}.pagerduty.com#{user.user_url}, #{emailNote}"
else
msg.send "I couldn't find your user :( #{emailNote}"
cmds = robot.helpCommands()
cmds = (cmd for cmd in cmds when cmd.match(/(pager me |who's on call)/))
msg.send cmds.join("\n")
robot.respond /pager(?: me)? as (.*)$/i, (msg) ->
email = msg.match[1]
msg.message.user.pagerdutyEmail = email
msg.send "Okay, I'll remember your PagerDuty email is #{email}"
# Assumes your Campfire usernames and PagerDuty names are identical
robot.respond /pager( me)? (\d+)/i, (msg) ->
withPagerDutyUser msg, (user) ->
userId = user.id
return unless userId
start = moment().format()
minutes = parseInt msg.match[2]
end = moment().add('minutes', minutes).format()
override = {
'start': start,
'end': end,
'user_id': userId
}
withCurrentOncall msg, (old_username) ->
data = { 'override': override }
pagerDutyPost msg, "/schedules/#{pagerDutyScheduleId}/overrides", data, (json) ->
if json.override
start = moment(json.override.start)
end = moment(json.override.end)
msg.send "Rejoice, #{old_username}! #{json.override.user.name} has the pager until #{end.format()}"
robot.respond /(pager|major)( me)? incident (.*)$/, (msg) ->
pagerDutyIncident msg, msg.match[3], (incident) ->
msg.send formatIncident(incident)
robot.respond /(pager|major)( me)? (inc|incidents|sup|problems)$/i, (msg) ->
pagerDutyIncidents msg, "triggered,acknowledged", (incidents) ->
if incidents.length > 0
buffer = "Triggered:\n----------\n"
for junk, incident of incidents.reverse()
if incident.status == 'triggered'
buffer = buffer + formatIncident(incident)
buffer = buffer + "\nAcknowledged:\n-------------\n"
for junk, incident of incidents.reverse()
if incident.status == 'acknowledged'
buffer = buffer + formatIncident(incident)
msg.send buffer
else
msg.send "No open incidents"
robot.respond /(pager|major)( me)? (?:trigger|page) (.+)$/i, (msg) ->
user = msg.message.user.name
reason = msg.match[3]
description = "#{reason} - @#{user}"
pagerDutyIntegrationAPI msg, "trigger", description, (json) ->
msg.reply "#{json.status}, key: #{json.incident_key}"
robot.respond /(?:pager|major)(?: me)? ack(?:nowledge)? (.+)$/i, (msg) ->
incidentNumbers = parseIncidentNumbers(msg.match[1])
# only acknowledge triggered things, since it doesn't make sense to re-acknowledge if it's already in re-acknowledge
# if it ever doesn't need acknowledge again, it means it's timed out and has become 'triggered' again anyways
updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')
robot.respond /(pager|major)( me)? ack(nowledge)?$/i, (msg) ->
pagerDutyIncidents msg, 'triggered,acknwowledged', (incidents) ->
incidentNumbers = (incident.incident_number for incident in incidents)
if incidentNumbers.length < 1
msg.send "Nothing to acknowledge"
return
# only acknowledge triggered things
updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')
robot.respond /(?:pager|major)(?: me)? res(?:olve)?(?:d)? (.+)$/i, (msg) ->
incidentNumbers = parseIncidentNumbers(msg.match[1])
# allow resolving of triggered and acknowedlge, since being explicit
updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'resolved')
robot.respond /(pager|major)( me)? res(olve)?(d)?$/i, (msg) ->
pagerDutyIncidents msg, "acknowledged", (incidents) ->
incidentNumbers = (incident.incident_number for incident in incidents)
if incidentNumbers.length < 1
msg.send "Nothing to resolve"
return
# only resolve things that are acknowledged
updateIncidents(msg, incidentNumbers, 'acknowledged', 'resolved')
robot.respond /(pager|major)( me)? notes (.+)$/i, (msg) ->
incidentId = msg.match[3]
pagerDutyGet msg, "/incidents/#{incidentId}/notes", {}, (json) ->
buffer = ""
for note in json.notes
buffer += "#{note.created_at} #{note.user.name}: #{note.content}\n"
msg.send buffer
robot.respond /(pager|major)( me)? note ([\d\w]+) (.+)$/i, (msg) ->
incidentId = msg.match[3]
content = msg.match[4]
withPagerDutyUser msg, (user) ->
userId = user.id
return unless userId
data =
note:
content: content
requester_id: userId
pagerDutyPost msg, "/incidents/#{incidentId}/notes", data, (json) ->
if json && json.note
msg.send "Got it! Note created: #{json.note.content}"
else
msg.send "Sorry, I couldn't do it :("
# who is on call?
robot.respond /who('s|s| is)? (on call|oncall)/i, (msg) ->
withCurrentOncall msg, (username) ->
msg.reply "#{username} is on call"
parseIncidentNumbers = (match) ->
match.split(/[ ,]+/).map (incidentNumber) ->
parseInt(incidentNumber)
missingEnvironmentForApi = (msg) ->
missingAnything = false
unless pagerDutySubdomain?
msg.send "PagerDuty Subdomain is missing: Ensure that HUBOT_PAGERDUTY_SUBDOMAIN is set."
missingAnything |= true
unless pagerDutyApiKey?
msg.send "PagerDuty API Key is missing: Ensure that HUBOT_PAGERDUTY_API_KEY is set."
missingAnything |= true
unless pagerDutyScheduleId?
msg.send "PagerDuty Schedule ID is missing: Ensure that HUBOT_PAGERDUTY_SCHEDULE_ID is set."
missingAnything |= true
missingAnything
withPagerDutyUser = (msg, cb) ->
email = msg.message.user.pagerdutyEmail || msg.message.user.email_address
unless email
msg.send "Sorry, I can't figure out your email address :( Can you tell me with `#{robot.name} pager me as [email protected]`?"
return
pagerDutyGet msg, "/users", {query: email}, (json) ->
if json.users.length isnt 1
msg.send "Sorry, I expected to get 1 user back for #{email}, but got #{json.users.length} :sweat:"
return
cb(json.users[0])
pagerDutyGet = (msg, url, query, cb) ->
if missingEnvironmentForApi(msg)
return
auth = "Token token=#{pagerDutyApiKey}"
msg.http(pagerDutyBaseUrl + url)
.query(query)
.headers(Authorization: auth, Accept: 'application/json')
.get() (err, res, body) ->
json_body = null
switch res.statusCode
when 200 then json_body = JSON.parse(body)
else
console.log res.statusCode
console.log body
json_body = null
cb json_body
pagerDutyPut = (msg, url, data, cb) ->
if missingEnvironmentForApi(msg)
return
json = JSON.stringify(data)
auth = "Token token=#{pagerDutyApiKey}"
msg.http(pagerDutyBaseUrl + url)
.headers(Authorization: auth, Accept: 'application/json')
.header("content-type","application/json")
.header("content-length",json.length)
.put(json) (err, res, body) ->
json_body = null
switch res.statusCode
when 200 then json_body = JSON.parse(body)
else
console.log res.statusCode
console.log body
json_body = null
cb json_body
pagerDutyPost = (msg, url, data, cb) ->
if missingEnvironmentForApi(msg)
return
json = JSON.stringify(data)
auth = "Token token=#{pagerDutyApiKey}"
msg.http(pagerDutyBaseUrl + url)
.headers(Authorization: auth, Accept: 'application/json')
.header("content-type","application/json")
.header("content-length",json.length)
.post(json) (err, res, body) ->
json_body = null
switch res.statusCode
when 201 then json_body = JSON.parse(body)
else
console.log res.statusCode
console.log body
json_body = null
cb json_body
withCurrentOncall = (msg, cb) ->
oneHour = moment().add('hours', 1).format()
now = moment().format()
query = {
since: now,
until: oneHour,
overflow: 'true'
}
pagerDutyGet msg, "/schedules/#{pagerDutyScheduleId}/entries", query, (json) ->
if json.entries and json.entries.length > 0
cb(json.entries[0].user.name)
pagerDutyIncident = (msg, incident, cb) ->
pagerDutyGet msg, "/incidents/#{encodeURIComponent incident}", {}, (json) ->
cb(json)
pagerDutyIncidents = (msg, status, cb) ->
query =
status: status
sort_by: "incident_number:asc"
pagerDutyGet msg, "/incidents", query, (json) ->
cb(json.incidents)
pagerDutyIntegrationAPI = (msg, cmd, description, cb) ->
unless pagerDutyServiceApiKey?
msg.send "PagerDuty API service key is missing."
msg.send "Ensure that HUBOT_PAGERDUTY_SERVICE_API_KEY is set."
return
data = null
switch cmd
when "trigger"
data = JSON.stringify { service_key: pagerDutyServiceApiKey, event_type: "trigger", description: description}
pagerDutyIntergrationPost msg, data, (json) ->
cb(json)
formatIncident = (inc) ->
# { pd_nagios_object: 'service',
# HOSTNAME: 'fs1a',
# SERVICEDESC: 'snapshot_repositories',
# SERVICESTATE: 'CRITICAL',
# HOSTSTATE: 'UP' },
summary = if inc.trigger_summary_data
# email services
if inc.trigger_summary_data.subject
inc.trigger_summary_data.subject
else if inc.trigger_summary_data.description
inc.trigger_summary_data.description
else if inc.trigger_summary_data.pd_nagios_object == 'service'
"#{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.SERVICEDESC}"
else if inc.trigger_summary_data.pd_nagios_object == 'host'
"#{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.HOSTSTATE}"
else
""
else
""
assigned_to = if inc.assigned_to_user
"- assigned to #{inc.assigned_to_user.name}"
else
""
"#{inc.incident_number}: #{inc.created_on} #{summary} #{assigned_to}\n"
updateIncidents = (msg, incidentNumbers, statusFilter, updatedStatus) ->
withPagerDutyUser msg, (user) ->
requesterId = user.id
return unless requesterId
pagerDutyIncidents msg, statusFilter, (incidents) ->
foundIncidents = []
for incident in incidents
# FIXME this isn't working very consistently
if incidentNumbers.indexOf(incident.incident_number) > -1
foundIncidents.push(incident)
if foundIncidents.length == 0
msg.reply "Couldn't find incidents #{incidentNumbers.join(', ')} in #{inspect incidents}"
else
# loljson
data = {
requester_id: requesterId
incidents: foundIncidents.map (incident) ->
{
'id': incident.id,
'status': updatedStatus
}
}
pagerDutyPut msg, "/incidents", data , (json) ->
if json?.incidents
buffer = "Incident"
buffer += "s" if json.incidents.length > 1
buffer += " "
buffer += (incident.incident_number for incident in json.incidents).join(", ")
buffer += " #{updatedStatus}"
msg.reply buffer
else
msg.reply "Problem updating incidents #{incidentNumbers.join(',')}"
pagerDutyIntergrationPost = (msg, json, cb) ->
msg.http('https://events.pagerduty.com/generic/2010-04-15/create_event.json')
.header("content-type","application/json")
.header("content-length", json.length)
.post(json) (err, res, body) ->
switch res.statusCode
when 200
json = JSON.parse(body)
cb(json)
else
console.log res.statusCode
console.log body
# Pagerduty Webhook Integration (For a payload example, see http://developer.pagerduty.com/documentation/rest/webhooks)
parseWebhook = (req, res) ->
hook = req.body
messages = hook.messages
if /^incident.*$/.test(messages[0].type)
parseIncidents(messages)
else
"No incidents in webhook"
getUserForIncident = (incident) ->
if incident.assigned_to_user
incident.assigned_to_user.email
else if incident.resolved_by_user
incident.resolved_by_user.email
else
'(???)'
generateIncidentString = (incident, hookType) ->
console.log "hookType is " + hookType
if hookType == "incident.trigger"
"""
Incident # #{incident.incident_number} :
#{incident.status} and assigned to #{getUserForIncident(incident)}
#{incident.html_url}
"""
else if hookType == "incident.acknowledge"
"""
Incident # #{incident.incident_number} :
#{incident.status} and assigned to #{getUserForIncident(incident)}
#{incident.html_url}
"""
else if hookType == "incident.resolve"
"""
Incident # #{incident.incident_number} has been resolved by #{getUserForIncident(incident)}
#{incident.html_url}
"""
else if hookType == "incident.unacknowledge"
"""
#{incident.status} , unacknowledged and assigned to #{getUserForIncident(incident)}
#{incident.html_url}
"""
else if hookType == "incident.assign"
"""
Incident # #{incident.incident_number} :
#{incident.status} , reassigned to #{getUserForIncident(incident)}
#{incident.html_url}
"""
else if hookType == "incident.escalate"
"""
Incident # #{incident.incident_number} :
#{incident.status} , was escalated and assigned to #{getUserForIncident(incident)}
#{incident.html_url}
"""
parseIncidents = (messages) ->
returnMessage = []
count = 0
for message in messages
incident = message.data.incident
hookType = message.type
returnMessage.push(generateIncidentString(incident, hookType))
count = count+1
returnMessage.unshift("You have " + count + " PagerDuty update(s): \n")
returnMessage.join("\n")
# Webhook listener
if pagerEndpoint && pagerRoom
robot.router.post pagerEndpoint, (req, res) ->
robot.messageRoom(pagerRoom, parseWebhook(req,res))
res.end()