pagerduty-overlap-checker
Advanced tools
Comparing version 1.1.1 to 1.2.0
// Generated by CoffeeScript 1.10.0 | ||
var Slack, async, createPagerDutyIncident, createSlackMessage, debug, formatMessage, nconf, request, send; | ||
var Slack, async, createPagerDutyIncident, createSlackMessage, debug, formatMessage, nconf, pdApi, request, send; | ||
@@ -14,21 +14,55 @@ Slack = require('node-slackr'); | ||
pdApi = require('./pagerduty-api'); | ||
createPagerDutyIncident = function(options, message, cb) { | ||
debug("Creating PD incident " + options.description); | ||
if (!options.serviceKey) { | ||
return cb(new Error("Missing PD service key")); | ||
var incident, incidentOptions; | ||
debug("Creating PD incident " + (JSON.stringify(message)) + " with options " + (JSON.stringify(options))); | ||
if (!(options.pdToken && options.serviceId && options.from)) { | ||
cb(new Error("Missing PAGERDUTY settings (you'll need PAGERDUTY_TOKEN, PAGERDUTY_SERVICE_ID and PAGERDUTY_ESCALATION_POLICY_ID)")); | ||
} | ||
if (!(message.userId || options.escalationPolicyId)) { | ||
return cb(new Error("No userId or escalation policy specified")); | ||
} else { | ||
return request({ | ||
uri: 'https://events.pagerduty.com/generic/2010-04-15/create_event.json', | ||
incident = { | ||
type: "incident", | ||
title: "On-call overlap found!", | ||
service: { | ||
id: options.serviceId, | ||
type: "service_reference" | ||
}, | ||
body: { | ||
type: "incident_body", | ||
details: message.messages.join('\n') | ||
} | ||
}; | ||
if (options.escalationPolicyId) { | ||
incident.escalationPolicy = { | ||
id: options.escalationPolicyId, | ||
type: "escalation_policy_reference" | ||
}; | ||
} else { | ||
incident.assignments = [ | ||
{ | ||
assignee: { | ||
id: message.userId, | ||
type: "user_reference" | ||
} | ||
} | ||
]; | ||
} | ||
incidentOptions = { | ||
method: "POST", | ||
json: { | ||
service_key: options.serviceKey, | ||
event_type: "trigger", | ||
description: "On-call overlap found!", | ||
details: message | ||
incident: incident | ||
}, | ||
headers: { | ||
From: options.from, | ||
Authorization: 'Token token=' + options.pdToken | ||
} | ||
}, function(err, res, body) { | ||
}; | ||
return pdApi.send('/incidents', incidentOptions, function(err, res, body) { | ||
var ref; | ||
if ((body != null ? (ref = body.errors) != null ? ref.length : void 0 : void 0) > 0) { | ||
if (err == null) { | ||
err = new Error("INCIDENT_CREATION_FAILED Cannot create event: " + (JSON.stringify(body.errors))); | ||
err = new Error("INCIDENT_CREATION_FAILED Errors: " + (JSON.stringify(body.errors))); | ||
} | ||
@@ -78,3 +112,3 @@ } | ||
message = messages[i]; | ||
outputMessage += message.user + ": " + message.schedules[0] + " and " + message.schedules[1] + " on " + (message.date.toLocaleString()) + "\n"; | ||
outputMessage += message.user + ": " + message.schedules[0] + " and " + message.schedules[1] + " on " + (message.date.toUTCString()) + "\n"; | ||
} | ||
@@ -86,3 +120,3 @@ break; | ||
message = messages[j]; | ||
outputMessage += "*" + message.user + ":* `" + message.schedules[0] + "` and `" + message.schedules[1] + "` on " + (message.date.toLocaleString()) + "\n"; | ||
outputMessage += "*" + message.user + ":* `" + message.schedules[0] + "` and `" + message.schedules[1] + "` on " + (message.date.toUTCString()) + "\n"; | ||
} | ||
@@ -92,7 +126,16 @@ break; | ||
outputMessage = messages.reduce(function(acc, curr) { | ||
var name; | ||
if (acc[name = curr.user] == null) { | ||
acc[name] = []; | ||
var base, base1, base2, name; | ||
if (acc[name = curr.userId] == null) { | ||
acc[name] = {}; | ||
} | ||
acc[curr.user].push(curr.schedules[0] + " and " + curr.schedules[1] + " on " + (curr.date.toLocaleString())); | ||
if ((base = acc[curr.userId]).userId == null) { | ||
base.userId = curr.userId; | ||
} | ||
if ((base1 = acc[curr.userId]).user == null) { | ||
base1.user = curr.user; | ||
} | ||
if ((base2 = acc[curr.userId]).messages == null) { | ||
base2.messages = []; | ||
} | ||
acc[curr.userId].messages.push(curr.schedules[0] + " and " + curr.schedules[1] + " " + (curr.date.toUTCString())); | ||
return acc; | ||
@@ -125,11 +168,20 @@ }, {}); | ||
}, function(next) { | ||
var pdOptions; | ||
if (options['PAGERDUTY_TOKEN'] != null) { | ||
var messagesByUser, pdOptions, ref; | ||
if (!options['PAGERDUTY'] && !options['PAGERDUTY_TOKEN']) { | ||
return debug('No PAGERDUTY options defined'); | ||
} else if ((options['PAGERDUTY']['PAGERDUTY_TOKEN'] || options['PAGERDUTY_TOKEN']) && options['PAGERDUTY']['PAGERDUTY_SERVICE_ID'] && options['PAGERDUTY']['PAGERDUTY_FROM']) { | ||
debug('Found PD token - creating an incident'); | ||
pdOptions = {}; | ||
pdOptions.serviceKey = options['PAGERDUTY_TOKEN']; | ||
pdOptions.description = message; | ||
return createPagerDutyIncident(pdOptions, formatMessage(message, 'json'), next); | ||
pdOptions.pdToken = options['PAGERDUTY']['PAGERDUTY_TOKEN'] || options['PAGERDUTY_TOKEN']; | ||
pdOptions.serviceId = options['PAGERDUTY']['PAGERDUTY_SERVICE_ID']; | ||
pdOptions.escalationPolicyId = options['PAGERDUTY']['PAGERDUTY_ESCALATION_POLICY_ID']; | ||
pdOptions.from = (ref = options['PAGERDUTY']) != null ? ref['PAGERDUTY_FROM'] : void 0; | ||
messagesByUser = formatMessage(message, 'json'); | ||
return async.each(messagesByUser, function(item, cb) { | ||
return createPagerDutyIncident(pdOptions, item, cb); | ||
}, function(err) { | ||
return next(err); | ||
}); | ||
} else { | ||
debug('No PD token defined'); | ||
console.log("No PD options defined or defined incorrectly (" + (JSON.stringify(options['PAGERDUTY'])) + ")"); | ||
return next(); | ||
@@ -136,0 +188,0 @@ } |
// Generated by CoffeeScript 1.10.0 | ||
var _, async, checkSchedulesIds, debug, getDayAbbrev, getSchedule, getSchedulesIds, getUserId, nconf, notify, overrideUser, pdGet, processSchedules, processSchedulesFromConfig, request, sendNotification, | ||
var _, async, checkSchedulesIds, debug, getDayAbbrev, getSchedule, getSchedulesIds, nconf, notify, pdApi, processSchedules, processSchedulesFromConfig, sendNotification, | ||
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | ||
@@ -7,4 +7,2 @@ | ||
request = require('request'); | ||
nconf = require('nconf'); | ||
@@ -18,21 +16,3 @@ | ||
pdGet = function(endpointPath, overrideOptions, cb) { | ||
var sharedOptions; | ||
debug("Calling " + endpointPath + " with options:", overrideOptions); | ||
sharedOptions = { | ||
uri: nconf.get('PAGERDUTY_API_URL') + endpointPath, | ||
method: 'GET', | ||
json: true, | ||
headers: { | ||
'Authorization': 'Token token=' + nconf.get('PAGERDUTY_READ_ONLY_TOKEN') | ||
} | ||
}; | ||
if (typeof overrideOptions === 'function') { | ||
cb = overrideOptions; | ||
overrideOptions = {}; | ||
} | ||
_.extend(sharedOptions, overrideOptions); | ||
debug('Calling request with: ', sharedOptions); | ||
return request(sharedOptions, cb); | ||
}; | ||
pdApi = require('./pagerduty-api'); | ||
@@ -46,3 +26,4 @@ getSchedule = function(id, cb) { | ||
scheduleOpts = { | ||
form: { | ||
qs: { | ||
'schedule_ids[]': id, | ||
until: timeUntil.toISOString(), | ||
@@ -52,3 +33,7 @@ since: timeNow.toISOString() | ||
}; | ||
return pdGet("/schedules/" + id + "/entries", scheduleOpts, function(err, res, body) { | ||
return pdApi.send("/oncalls", scheduleOpts, function(err, res, body) { | ||
if (err) { | ||
console.log("Request send error:", err); | ||
return cb(err); | ||
} | ||
if (res.statusCode !== 200) { | ||
@@ -59,3 +44,3 @@ return cb(new Error("Entries returned status code " + res.statusCode)); | ||
id: id, | ||
entries: body.entries | ||
entries: body.oncalls | ||
}); | ||
@@ -70,9 +55,9 @@ }); | ||
debug("Getting schedules from PD"); | ||
return pdGet("/schedules", {}, function(err, res, body) { | ||
return pdApi.send("/schedules", {}, function(err, res, body) { | ||
var j, len, ref, schedule, schedulesIds; | ||
debug('Returned status code:', res.statusCode); | ||
if (err) { | ||
console.log("Cannot get request:", err); | ||
console.log("Request send error:", err); | ||
return cb(err); | ||
} | ||
debug('Returned status code:', res.statusCode); | ||
schedulesIds = []; | ||
@@ -86,3 +71,3 @@ ref = body.schedules; | ||
debug("Schedules Ids from PD: ", schedulesIds); | ||
debug("Schedules Names from PD: ", debug(nconf.get("schedulesNames"))); | ||
debug("Schedules Names from PD: ", nconf.get("schedulesNames")); | ||
return cb(null, schedulesIds); | ||
@@ -100,4 +85,4 @@ }); | ||
} | ||
debug("Schedules Ids from config: ", _.flatten(listIds)); | ||
configSchedulesIds = _.uniq(_.flatten(listIds)); | ||
debug("Schedules Ids from config: ", configSchedulesIds); | ||
return getSchedulesIds(function(err, schedulesIds) { | ||
@@ -172,16 +157,13 @@ if (err) { | ||
schedule = allSchedules[j]; | ||
debug('schedule:', schedule); | ||
debug('schedule:', JSON.stringify(schedule)); | ||
otherSchedules = _.without(allSchedules, schedule); | ||
debug('otherSchedules:', otherSchedules); | ||
debug('otherSchedules:', JSON.stringify(otherSchedules)); | ||
ref1 = schedule.entries; | ||
for (k = 0, len1 = ref1.length; k < len1; k++) { | ||
entry = ref1[k]; | ||
debug('checking entry: ', JSON.stringify(entry)); | ||
myStart = entry.start; | ||
debug(myStart); | ||
myEnd = entry.end; | ||
debug(myEnd); | ||
myUserId = entry.user.id; | ||
debug(myUserId); | ||
myUserName = entry.user.name; | ||
debug(myUserName); | ||
myUserName = entry.user.summary; | ||
if (duplicities.myUserName == null) { | ||
@@ -202,2 +184,3 @@ duplicities.myUserName = []; | ||
user: myUserName, | ||
userId: myUserId, | ||
schedules: [scheduleId, crossScheduleId], | ||
@@ -243,57 +226,2 @@ date: startDate | ||
getUserId = function(email, cb) { | ||
var userOptions; | ||
userOptions = { | ||
form: { | ||
query: email | ||
} | ||
}; | ||
return pdGet("/users", userOptions, function(err, res, body) { | ||
var userId; | ||
if (res.statusCode !== 200) { | ||
return cb(new Error("Entries returned status code " + res.statusCode)); | ||
} | ||
userId = body.users[0].id; | ||
return cb(err, userId); | ||
}); | ||
}; | ||
overrideUser = function(userId, scheduleId, durationInMinutes, cb) { | ||
var duration, endDate, sharedOptions, startDate; | ||
if (durationInMinutes == null) { | ||
durationInMinutes = 30; | ||
} | ||
if (userId && scheduleId) { | ||
duration = durationInMinutes * 60 * 1000; | ||
startDate = new Date(); | ||
endDate = new Date(startDate.getTime() + duration); | ||
sharedOptions = { | ||
uri: nconf.get('PAGERDUTY_API_URL') + ("/schedules/" + scheduleId + "/overrides"), | ||
method: 'POST', | ||
headers: { | ||
'Authorization': 'Token token=' + nconf.get('PAGERDUTY_TOKEN') | ||
}, | ||
form: { | ||
override: { | ||
"user_id": userId, | ||
"start": startDate.toISOString(), | ||
"end": endDate.toISOString() | ||
} | ||
} | ||
}; | ||
debug('Calling request with: ', sharedOptions); | ||
return request(sharedOptions, function(err, res, body) { | ||
var reponseObject; | ||
if (err) { | ||
return cb(err); | ||
} | ||
if (res.statusCode !== 201) { | ||
return cb(new Error("Entries returned status code " + res.statusCode)); | ||
} | ||
reponseObject = JSON.parse(body); | ||
return cb(err, reponseObject.override); | ||
}); | ||
} | ||
}; | ||
getDayAbbrev = function(utcDay) { | ||
@@ -306,3 +234,2 @@ var days; | ||
module.exports = { | ||
pdGet: pdGet, | ||
getSchedulesIds: getSchedulesIds, | ||
@@ -312,5 +239,3 @@ checkSchedulesIds: checkSchedulesIds, | ||
processSchedulesFromConfig: processSchedulesFromConfig, | ||
sendNotification: sendNotification, | ||
getUserId: getUserId, | ||
overrideUser: overrideUser | ||
sendNotification: sendNotification | ||
}; |
{ | ||
"name": "pagerduty-overlap-checker", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "PagerDuty Overlap Duties Checker", | ||
@@ -5,0 +5,0 @@ "main": "lib/", |
@@ -7,4 +7,4 @@ [![Build Status](https://travis-ci.org/apiaryio/pagerduty-overlap-checker.svg?branch=master)](https://travis-ci.org/apiaryio/pagerduty-overlap-checker) | ||
- 4.3 (LTS) | ||
- 5.x latest | ||
- 5.x | ||
- 6.x (LTS) | ||
@@ -19,29 +19,41 @@ # Pager Duty Overrides Checker | ||
- `SCHEDULES` array can contain one or more `SCHEDULE` items to check | ||
- every `SCHEDULE` should have a `NOTIFICATIONS` to create incident or send message if overlap is found | ||
- every `SCHEDULE` should have a `NOTIFICATIONS` section to create a PagerDuty incident or send a Slack message if overlap is found | ||
- `SCHEDULE` can contain a `EXCLUSION_DAYS` key, which specifies days (3 letter abb.) in form of object with optional `start` and `end` time (`hh:mm` format **UTC TIMEZONE**).If `start` or `end` is omitted, whole day is considered excluded. | ||
Example below represents current weekend on-call setup. | ||
Currently, we only support Slack (`SLACK` with `SLACK_WEBHOOK_URL` and `CHANNEL`) or shorthanded `SLACK_WEBHOOK_URL` and PagerDuty (`PAGERDUTY_TOKEN`) notifications. | ||
Currently, we support Slack (`SLACK` with `SLACK_WEBHOOK_URL` and `CHANNEL`) or shorthanded `SLACK_WEBHOOK_URL` and | ||
PagerDuty (`PAGERDUTY` with `PAGERDUTY_TOKEN`, `PAGERDUTY_SERVICE_ID` and `PAGERDUTY_FROM`) notifications. The | ||
`PAGERDUTY_TOKEN` has to have full access. | ||
For PagerDuty, an incident can either be directly assigned to the user with overlaps (default) or set to an escalation | ||
policy (if specified by `PAGERDUTY_ESCALATION_POLICY_ID` in config). | ||
All PagerDuty integrations are using [PagerDuty API v2](https://v2.developer.pagerduty.com/v2/page/api-reference#!/API_Reference/get_api_reference). | ||
When generating an API token, select the v2 option. | ||
```json | ||
{ | ||
"PAGERDUTY_API_URL": "https://acme.pagerduty.com/api/v1", | ||
"PAGERDUTY_READ_ONLY_TOKEN": "33333333333333333333", | ||
"WEEKS_TO_CHECK": 2, | ||
"SCHEDULES": [{ | ||
"SCHEDULE": ["PWEVPB6", "PT57OLG"], | ||
"NOTIFICATIONS": { | ||
"SLACK": { | ||
"SLACK_WEBHOOK_URL": "http://acme.slack.com/11111", | ||
"CHANNEL": "#channel-name" | ||
} | ||
} | ||
}, { | ||
"SCHEDULE": ["PWEVPB6", "PT57OLA"], | ||
"NOTIFICATIONS": { | ||
"PAGERDUTY_TOKEN": "22222222222222222222", | ||
"SLACK_WEBHOOK_URL": "http://acme.slack.com/11111", | ||
}, | ||
"EXCLUSION_DAYS": {"Fri": {"start": "14:00", "end": "23:59"}, "Sat": {}, "Sun": {"start": "00:00", "end": "14:00"}} | ||
}] | ||
"PAGERDUTY_API_URL": "https://acme.pagerduty.com/api/v1", | ||
"PAGERDUTY_READ_ONLY_TOKEN": "33333333333333333333", | ||
"WEEKS_TO_CHECK": 2, | ||
"SCHEDULES": [{ | ||
"SCHEDULE": ["PWEVPB6", "PT57OLG"], | ||
"NOTIFICATIONS": { | ||
"SLACK": { | ||
"SLACK_WEBHOOK_URL": "http://acme.slack.com/11111", | ||
"CHANNEL": "#channel-name" | ||
} | ||
} | ||
}, { | ||
"SCHEDULE": ["PWEVPB6", "PT57OLA"], | ||
"NOTIFICATIONS": { | ||
"PAGERDUTY": { | ||
"PAGERDUTY_TOKEN": "22222222222222222222", | ||
"PAGERDUTY_SERVICE_ID": "PFARE53", | ||
"PAGERDUTY_FROM": "test@test.com" | ||
}, | ||
"SLACK_WEBHOOK_URL": "http://acme.slack.com/11111" | ||
}, | ||
"EXCLUSION_DAYS": {"Fri": {"start": "14:00", "end": "23:59"}, "Sat": {}, "Sun": {"start": "00:00", "end": "14:00"}} | ||
}] | ||
} | ||
@@ -48,0 +60,0 @@ ``` |
24478
10
467
76