Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Then add **hubot-pager-me** to your `external-scripts.json`:
| `HUBOT_PAGERDUTY_USER_ID` | No`*`` | The user ID of a PagerDuty user for your bot. This is only required if you want chat users to be able to trigger incidents without their own PagerDuty user.
| `HUBOT_PAGERDUTY_SERVICE_API_KEY` | No`*`` | The [Incident Service Key](https://v2.developer.pagerduty.com/docs/incident-creation-api) to use when creating a new incident. This should be assigned to a dummy escalation policy that doesn't actually notify, as Hubot will trigger on this before reassigning it.
| `HUBOT_PAGERDUTY_SERVICES` | No | Provide a comma separated list of service identifiers (e.g. `PFGPBFY,AFBCGH`) to restrict queries to only those services. |
| `HUBOT_PAGERDUTY_TEAMS` | No | Provide a comma separated list of teams identifiers (e.g. `PFGPBFY,AFBCGH`) to restrict queries to only those teams. You need teams function on |
| `HUBOT_PAGERDUTY_SCHEDULES` | No | Provide a comma separated list of schedules identifiers (e.g. `PFGPBFY,AFBCGH`) to restrict queries to only those schedules. |

`*` - May be required for certain actions.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"async": "^2.6.1",
"lodash": "^4.17.10",
"moment-timezone": "~0.5.21",
"scoped-http-client": "^0.11.0"
},
Expand Down
43 changes: 42 additions & 1 deletion src/pagerduty.coffee
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
HttpClient = require 'scoped-http-client'
_ = require('lodash')
moment = require('moment-timezone')

pagerDutyApiKey = process.env.HUBOT_PAGERDUTY_API_KEY
pagerDutySubdomain = process.env.HUBOT_PAGERDUTY_SUBDOMAIN
pagerDutyBaseUrl = 'https://api.pagerduty.com'
pagerDutyServices = process.env.HUBOT_PAGERDUTY_SERVICES
pagerDutyTeams = process.env.HUBOT_PAGERDUTY_TEAMS
pagerDutySchedules = process.env.HUBOT_PAGERDUTY_SCHEDULES
pagerDutyFromEmail = process.env.HUBOT_PAGERDUTY_FROM_EMAIL
pagerNoop = process.env.HUBOT_PAGERDUTY_NOOP
pagerNoop = false if pagerNoop is 'false' or pagerNoop is 'off'
Expand Down Expand Up @@ -33,8 +37,11 @@ module.exports =
cb = query
query = {}

if pagerDutyTeams? && url.match /\/incidents/
query['teams_ids[]'] = pagerDutyTeams.split(',')

if pagerDutyServices? && url.match /\/incidents/
query['service'] = pagerDutyServices
query['service_ids[]'] = pagerDutyServices.split(',')

@http(url)
.query(query)
Expand Down Expand Up @@ -129,6 +136,40 @@ module.exports =
return
cb(null, json.incidents)

getOncalls: (query, tz, cb) ->
if typeof(query) is 'function'
cb = query
query = {}

if pagerDutySchedules?
query['schedule_ids[]'] = pagerDutySchedules.split(',')

if pagerDutyEscalationsPolicies?
query['escalation_policy_ids[]'] = pagerDutyEscalationsPolicies.split(',')

console.error query

@get "/oncalls", query, (err, json) ->
if err?
cb(err)
return
# escalation_level filtering
oncalls = _.map json.oncalls, (o) ->
if o.escalation_level is 1 then return o
filterdOncalls = _.without(oncalls, undefined)
timezone = tz
oncallsBySchedules = _.transform(filterdOncalls, (result, value, key) ->
message = "(#{moment(value.start).tz(timezone).format('MMM Do, h:mm a')} "
message += "- #{moment(value.end).tz(timezone).format('MMM Do, h:mm a')}) "
message += "- *#{value.user.summary}*"
unless result[value.schedule.summary]
(result[value.schedule.summary] || (result[value.schedule.summary] = [])).push(message);
if result[value.schedule.summary].indexOf(message) == -1
(result[value.schedule.summary] || (result[value.schedule.summary] = [])).push(message);
, {})

cb(null, oncallsBySchedules)

getSchedules: (query, cb) ->
if typeof(query) is 'function'
cb = query
Expand Down
198 changes: 152 additions & 46 deletions src/scripts/pagerduty.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pagerduty = require('../pagerduty')
async = require('async')
inspect = require('util').inspect
moment = require('moment-timezone')
_ = require('lodash')

pagerDutyUserId = process.env.HUBOT_PAGERDUTY_USER_ID
pagerDutyServiceApiKey = process.env.HUBOT_PAGERDUTY_SERVICE_API_KEY
Expand Down Expand Up @@ -600,42 +601,18 @@ module.exports = (robot) ->
else
msg.send 'No schedules found!'

# who is on call?
robot.respond /who(?:’s|'s|s| is|se)? (?:on call|oncall|on-call)(?:\?)?(?: (?:for )?((["'])([^]*?)\2|(.*?))(?:\?|$))?$/i, (msg) ->
if pagerduty.missingEnvironmentForApi(msg)
return

scheduleName = msg.match[3] or msg.match[4]
# who was on call?
robot.respond /who was (on call|oncall)/i, (msg) ->
getOncalls msg, -72

messages = []
renderSchedule = (s, cb) ->
withCurrentOncall msg, s, (username, schedule) ->
if (username)
messages.push("* #{username} is on call for #{schedule.name} - #{schedule.html_url}")
else
robot.logger.debug "No user for schedule #{schedule.name}"
cb null
# who is next on call?
robot.respond /who(?:’s|'s|s| is|se)? ((next (on call|oncall))|((on call|oncall) next))/i, (msg) ->
getOncalls msg, 72

if scheduleName?
withScheduleMatching msg, scheduleName, (s) ->
renderSchedule s, (err) ->
if err?
robot.emit 'error'
return
msg.send messages.join("\n")
else
pagerduty.getSchedules (err, schedules) ->
if err?
robot.emit 'error', err, msg
return
if schedules.length > 0
async.map schedules, renderSchedule, (err) ->
if err?
robot.emit 'error', err, msg
return
msg.send messages.join("\n")
else
msg.send 'No schedules found!'
# who is on call?
robot.respond /who(?:’s|'s|s| is|se)? (?:on call|oncall|on-call)(?:\?)?(?: (?:for )?((["'])([^]*?)\2|(.*?))(?:\?|$))?$/i, (msg) ->
getOncalls msg

robot.respond /(pager|major)( me)? services$/i, (msg) ->
if pagerduty.missingEnvironmentForApi(msg)
Expand Down Expand Up @@ -686,6 +663,61 @@ module.exports = (robot) ->
else
msg.send "That didn't work. Check Hubot's logs for an error!"


getOncalls = (msg, hoursSpan) ->
if pagerduty.missingEnvironmentForApi(msg)
return

messages = []

oncallName = msg.match[3] or msg.match[4]

if oncallName?.trim() is 'next'
return

query = {
since: moment().format(),
until: moment().add(1, 'minute').format(),
earliest: true
}

if hoursSpan
if hoursSpan > 0
query.since = moment().format()
query.until = moment().add(hoursSpan, 'hours').format()
else
query.since = moment().subtract(-hoursSpan, 'hours').format()
query.until = moment().format()

pagerduty.getOncalls query, msg.message.user.slack.slack.tz or 'UTC', (err, oncalls) ->
if err?
robot.emit 'error', err, msg
return
if Object.keys(oncalls).length > 0
filteredOncalls = oncalls
_.forEach filteredOncalls, (value, key) ->
if hoursSpan < 0
values = value[value.length - 1]
else if hoursSpan > 0
if value.length > 1
values = value[1]
else
values = value[0]
else
values = value.join(", ")

messages.push("> #{key} #{values}")
msg.send _.uniq(messages).sort().join('\n')
else
msg.send 'No oncall found!'

getFirstOncalls = (oncalls) ->
return oncalls

getLatestOncalls = (oncalls) ->
return oncalls


parseIncidentNumbers = (match) ->
match.split(/[ ,]+/).map (incidentNumber) ->
parseInt(incidentNumber)
Expand Down Expand Up @@ -753,6 +785,14 @@ module.exports = (robot) ->
cb(schedule) for schedule in schedules
return

withOncallMatching = (msg, q, cb) ->
OncallsMatching msg, q, (oncalls) ->
if oncalls?.length < 1
msg.send "I couldn't find any oncalls matching #{q}"
else
cb(oncall) for oncall in oncalls
return

reassignmentParametersForUserOrScheduleOrEscalationPolicy = (msg, string, cb) ->
if campfireUser = robot.brain.userForName(string)
campfireUserToPagerDutyUser msg, campfireUser, (user) ->
Expand Down Expand Up @@ -785,19 +825,26 @@ module.exports = (robot) ->
cb()

withCurrentOncall = (msg, schedule, cb) ->
withCurrentOncallUser msg, schedule, (user, s) ->
if (user)
cb(user.name, s)
else
cb(null, s)
withCurrentOncallUser 1, msg, schedule, (user, s) ->
cb(user?.name or 'Nobody', s)

withCurrentOncallId = (msg, schedule, cb) ->
withCurrentOncallUser msg, schedule, (user, s) ->
withCurrentOncallUser 1, msg, schedule, (user, s) ->
cb(user.id, user.name, s)

withCurrentOncallUser = (msg, schedule, cb) ->
oneHour = moment().add(1, 'hours').format()
now = moment().format()
withCurrentOncallUser = (addedHours = 1, msg, schedule, cb) ->
if typeof schedule is 'function'
cb = schedule
schedule = msg
msg = addedHours
addedHours = 1

if addedHours > 0
timeSince = moment().format()
timeUntil = moment().add(addedHours, 'hours').format()
else
timeSince = moment().subtract(-addedHours, 'hours').format()
timeUntil = moment().format()

scheduleId = schedule.id
if (schedule instanceof Array && schedule[0])
Expand All @@ -807,18 +854,77 @@ module.exports = (robot) ->
return

query = {
since: now,
until: oneHour,
since: timeSince
until: timeUntil
overflow: 'true'
}

pagerduty.get "/schedules/#{scheduleId}/users", query, (err, json) ->
if err?
robot.emit 'error', err, msg
return
if json.users and json.users.length > 0
cb(json.users[0], schedule)
if json.entries and json.entries.length > 0
if addedHours isnt 1 # custom hours [who (next|was) oncall]
if addedHours > 0
first = json.entries[0]
next = json.entries[1] or first
users = [
first.user
next.user
]
else if addedHours < 0
last = json.entries.pop()
prev = json.entries.pop() or last
users = [
prev.user
last.user
]
return cb(users, schedule)
cb(json.entries[0].user, schedule)
else
cb(null, schedule)


withTimeBasedOncall = (addedHours = 1, msg, cb) ->
renderSchedule = (s, cb) ->
withCurrentOncallUser addedHours, msg, s, (users, schedule = {}) ->
cb(null,
users.map((user) ->
"#{schedule.name} - *#{user.name}*"
)
)

pagerduty.getSchedules (err, schedules = []) ->
if err?
robot.emit 'error', err, msg
return

if schedules.length > 0
async.map schedules, renderSchedule, (err, results = []) ->
if err?
robot.emit 'error', err, msg
return
rows = []

# Mix these arrays:
# ['platform userA', 'platform userX']
# ['user-support userC', 'user-support userT']
# into:
# [
# 'platform userA and user-support userC'
# 'platform userX and user-support userT'
# ]

for userIndex in [0..(results[0]?.length - 1)] by 1
messageRow = []
for historyIndex in [0..(results.length - 1)] by 1
messageRow.push(results[historyIndex][userIndex])
rows.push(messageRow)
cb(rows)
else
msg.send 'No schedules found!'


pagerDutyIntegrationAPI = (msg, cmd, description, cb) ->
unless pagerDutyServiceApiKey?
msg.send "PagerDuty API service key is missing."
Expand Down