This repository has been archived by the owner on Jun 8, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #858 from jnewland/pagerduty
Open source @github's Hubot PagerDuty integration
- Loading branch information
Showing
1 changed file
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |