Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

Open source @github's Hubot PagerDuty integration #858

Merged
merged 17 commits into from Apr 12, 2013
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
329 changes: 329 additions & 0 deletions src/scripts/pagerduty.coffee
@@ -0,0 +1,329 @@
# 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 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 inicidents
# hubot pager me ack <incident> - ack incident #<incident>
# hubot pager me resolve <incident> - resolve incident #<incident>
#
# Dependencies:
# "moment": "1.6.2"
#
# 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

inspect = require('util').inspect

moment = require('moment')

pagerDutyUsers = {}
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

module.exports = (robot) ->
robot.respond /pager( me)?$/i, (msg) ->
if missingEnvironmentForApi(msg)
return

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 `hubot pager me as you@yourdomain.com`"
else
"I don't know your PagerDuty email. Change it with `#{robot.name} pager me as you@yourdomain.com`"

cmds = robot.helpCommands()
cmds = (cmd for cmd in cmds when cmd.match(/(pager me |who's on call)/))
msg.send emailNote, 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) ->
withPagerDutyUsers msg, (users) ->

userId = pagerDutyUserId(msg, users)
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)? (inc|incidents|sup|problems)$/i, (msg) ->
pagerDutyIncidents msg, (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) ->
pagerDutyIntegrationAPI msg, "trigger", msg.match[3], (json) ->
msg.reply "#{json.status}, key: #{json.incident_key}"

robot.respond /(pager|major)( me)? ack(nowledge)? (.+)$/i, (msg) ->
updateIncident(msg, msg.match[4], 'acknowledged')

robot.respond /(pager|major)( me)? res(olve)?(d)? (.+)$/i, (msg) ->
updateIncident(msg, msg.match[5], '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]

withPagerDutyUsers msg, (users) ->

userId = pagerDutyUserId(msg, users)
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"

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


pagerDutyUserId = (msg, users) ->
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 you@yourdomain.com`?"
return

user = users[email]

unless user
msg.send "Sorry, I couldn't find a PagerDuty user for #{email}. Double check you have a user, and that I know your PagerDuty email with `#{robot.name} pager me as you@yourdomain.com`"
return

users[email].id

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)

withPagerDutyUsers = (msg, cb) ->
if pagerDutyUsers['loaded'] != true
pagerDutyGet msg, "/users", {}, (json) ->
pagerDutyUsers['loaded'] = true
for user in json.users
pagerDutyUsers[user.id] = user
pagerDutyUsers[user.email] = user
pagerDutyUsers[user.name] = user
cb(pagerDutyUsers)
else
cb(pagerDutyUsers)

pagerDutyIncidents = (msg, cb) ->
query =
status: "triggered,acknowledged"
sort_by: "incident_number:asc"
pagerDutyGet msg, "/incidents", query, (json) ->
cb(json.incidents)

pagerDutyIntegrationAPI = (msg, cmd, args, 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: "#{args}"}
pagerDutyIntergrationPost msg, data, (json) ->
cb(json)

formatIncident = (inc) ->
# { pd_nagios_object: 'service',
# HOSTNAME: 'fs1a',
# SERVICEDESC: 'snapshot_repositories',
# SERVICESTATE: 'CRITICAL',
# HOSTSTATE: 'UP' },
if inc.incident_number
if inc.trigger_summary_data.description
"#{inc.incident_number}: #{inc.trigger_summary_data.description} - assigned to #{inc.assigned_to_user.name}\n"
else if inc.trigger_summary_data.pd_nagios_object == 'service'
"#{inc.incident_number}: #{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.SERVICEDESC} - assigned to #{inc.assigned_to_user.name}\n"
else if inc.trigger_summary_data.pd_nagios_object == 'host'
"#{inc.incident_number}: #{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.HOSTSTATE} - assigned to #{inc.assigned_to_user.name}\n"
else
""

updateIncident = (msg, incident_number, status) ->
withPagerDutyUsers msg, (users) ->
userId = pagerDutyUserId(msg, users)
return unless userId

pagerDutyIncidents msg, (incidents) ->
foundIncidents = []
for incident in incidents
if "#{incident.incident_number}" == incident_number
foundIncidents = [ incident ]
# loljson
data = {
requester_id: userId
incidents: [
{
'id': incident.id,
'status': status
}
]
}
pagerDutyPut msg, "/incidents", data, (json) ->
if incident = json.incidents[0]
msg.reply "Incident #{incident.incident_number} #{incident.status}."
else
msg.reply "Problem updating incident #{incident_number}"
if foundIncidents.length == 0
msg.reply "Couldn't find incident #{incident_number}"


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