Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
756f76f
commit 14ab943
Showing
4 changed files
with
381 additions
and
1 deletion.
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 |
---|---|---|
@@ -1,4 +1,114 @@ | ||
======= | ||
pdmaint | ||
======= | ||
|
||
A command line utility for scheduling and managing maintenance windows in PagerDuty | ||
A command line utility for scheduling and managing maintenance windows in PagerDuty. | ||
|
||
Requirements | ||
============ | ||
|
||
pdmaint uses the python ``pygerduty`` library to interact with the PagerDuty API. | ||
This library can be installed from `source <https://github.com/dropbox/pygerduty>`_ or via ``pip install pygerduty``. | ||
|
||
pdmaint also makes use of the ``pytz`` library for timezone data. | ||
This library can be installed from `source <http://pytz.sourceforge.net/>`_ or via ``pip install pytz``. | ||
|
||
Documentation | ||
============= | ||
|
||
To get started with pdmaint, copy the file pdmaint-sample to ~/.pdmaint and | ||
edit the [pagerduty] section. Include an API key for your PagerDuty account, | ||
an e-mail address associated with your PagerDuty account, and the name of | ||
your PagerDuty account. If you access PagerDuty via the url | ||
https://blargh.pagerduty.com then your account name is blargh. | ||
|
||
Also make sure to set your time zone properly. It should match the time zone | ||
specified in your Pager Duty account. For a list of time zone strings simply | ||
run the tzlist command. | ||
|
||
Once the [pagerduty] section of your ~/.pdmaint file is set up then invoke | ||
pdmaint with the "services" command and you should see output that | ||
summarizes your defined services. It should look something like this: | ||
|
||
$ pdmaint services | ||
Service name Service ID | ||
--------------------------------------------------- | ||
Production Alerts TV4ATV1 | ||
Staging Alerts TQDX1FV | ||
Test services TT42DPX | ||
|
||
You can now schedule maintenance for any of these services using the pdmaint | ||
command line: | ||
|
||
$ pdmaint schedule -d 30 -n "30 minute test" -i TT42DPX | ||
ID: TX3JWSD : 30 minute test | ||
created by: Bruce Pennypacker | ||
start time: Thu Oct 9 13:18:47 2014 -0400 | ||
end time : Thu Oct 9 13:48:46 2014 -0400 | ||
services: | ||
Test services | ||
|
||
And to end the maintenance window on PagerDuty: | ||
|
||
$ pdmaint delete TX3JWSD | ||
|
||
To schedule multiple services for maintenance just specify multiple service | ||
IDs separated by commas: | ||
|
||
$ pdmaint schedule -d 60 -n "maintenance work" -i TV4ATV1,TQDX1FV,TT42DPX | ||
ID: TP3DXQ1 : 30 minute test | ||
created by: Bruce Pennypacker | ||
start time: Thu Oct 9 13:23:11 2014 -0400 | ||
end time : Thu Oct 9 13:53:09 2014 -0400 | ||
services: | ||
Production Alerts | ||
Staging Alerts | ||
|
||
If you find yourself reguarly scheduling similar maintenance windows then | ||
you can define templates in your ~/.pdmaint configuration file. Sections in | ||
the configuration file whose name ends with _schedule define templates | ||
that let you quickly and easily schedule common maintenance windows. For | ||
example, the folloing block defines a 2 hour maintenance window for | ||
performing releases to two services and a 30 minute window for testing another. | ||
|
||
[release_schedule] | ||
duration=120 | ||
note=Release maintenance window | ||
ids=TV4ATV1,TQDX1FV | ||
|
||
[test_schedule] | ||
duration=30 | ||
note=Testing | ||
ids=TT42DPX | ||
|
||
|
||
With the above entry in your ~/.pdmaint you can now do the following: | ||
|
||
Get a list of defined schedules: | ||
|
||
$ pdmaint schedules | ||
release | ||
test | ||
|
||
Schedule a release maintenance window: | ||
|
||
$ pdmaint schedule release | ||
ID: TJP53AF : maintenance window | ||
created by: Bruce Pennypacker | ||
start time: Thu Oct 9 13:43:54 2014 -0400 | ||
end time : Thu Oct 9 14:13:53 2014 -0400 | ||
services: | ||
Production Alerts | ||
Staging Alerts | ||
|
||
If desired you can override a defined maintenance window. Suppose you know | ||
that a release will take an hour longer than usual: | ||
|
||
$ pdmaint schedule release -d 180 | ||
ID: TJQ51A5 : maintenance window | ||
created by: Bruce Pennypacker | ||
start time: Thu Oct 9 13:47:33 2014 -0400 | ||
end time : Thu Oct 9 16:47:32 2014 -0400 | ||
services: | ||
Production Alerts | ||
Staging Alerts |
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,230 @@ | ||
#!/usr/bin/env python | ||
|
||
#The MIT License (MIT) | ||
# | ||
#Copyright (c) 2014 Care.com | ||
# | ||
#Permission is hereby granted, free of charge, to any person obtaining a copy | ||
#of this software and associated documentation files (the "Software"), to deal | ||
#in the Software without restriction, including without limitation the rights | ||
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
#copies of the Software, and to permit persons to whom the Software is | ||
#furnished to do so, subject to the following conditions: | ||
# | ||
#The above copyright notice and this permission notice shall be included in all | ||
#copies or substantial portions of the Software. | ||
# | ||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
#SOFTWARE. | ||
|
||
# Based on the following: | ||
# https://github.com/dropbox/pygerduty | ||
# http://developer.pagerduty.com/documentation/rest | ||
|
||
import os | ||
import sys | ||
import argparse | ||
import ConfigParser | ||
import pygerduty | ||
import datetime | ||
from pytz import timezone | ||
import dateutil.parser | ||
|
||
try: | ||
import json | ||
except ImportError: | ||
import simplejson as json | ||
|
||
def print_window(window): | ||
stime = dateutil.parser.parse(window.start_time) | ||
etime = dateutil.parser.parse(window.end_time) | ||
desc = window.description | ||
if desc == None: | ||
desc = '' | ||
else: | ||
desc = ': ' + desc | ||
print "ID: %s %s" % (window.id, desc) | ||
print "created by: %s" % (window.created_by.name) | ||
print "start time: %s" % (stime.strftime("%c %z")) | ||
print "end time : %s" % (etime.strftime("%c %z")) | ||
print "services:" | ||
for s in window.services: | ||
print " %s" % (s.name) | ||
print "" | ||
|
||
def do_services(pager, args, config): | ||
print "Service name Service ID" | ||
print "---------------------------------------------------" | ||
for service in pager.services.list(): | ||
print "%-40s %s" % (service.name, service.id) | ||
|
||
def do_list(pager, args, config): | ||
# filter can be past, future, or ongoing. We don't care about | ||
# maintenance windows that occurred in the past, so only display | ||
# ongoing and then future maintenance windows | ||
# See http://developer.pagerduty.com/documentation/rest/maintenance_windows/list | ||
for window in pager.maintenance_windows.list(filter="ongoing"): | ||
print_window(window) | ||
|
||
for window in pager.maintenance_windows.list(filter="future"): | ||
print_window(window) | ||
|
||
def do_users(pager, args, config): | ||
print "Username e-mail userid" | ||
print "---------------------------------------------------------------------" | ||
for user in pager.users.list(): | ||
print "%-20s %-40s %s" % (user.name, user.email, user.id) | ||
|
||
def do_schedules(pager, args, config): | ||
for s in sorted(config.sections()): | ||
if s.endswith('_schedule'): | ||
print s[:-9] | ||
|
||
def do_schedule(pager, args, config): | ||
# First we have to find the PD userid based on the users e-mail | ||
# address | ||
userid = None | ||
for user in pager.users.list(): | ||
if user.email == email: | ||
userid = user.id | ||
break | ||
|
||
if userid == None: | ||
print "e-mail address %s not recognized in PagerDuty account %s" \ | ||
% (email, account) | ||
sys.exit(1) | ||
|
||
duration = None | ||
note = None | ||
ids = None | ||
|
||
# If args.option is defined then read default values from | ||
# the configuration file | ||
if args.option: | ||
cfg = args.option + "_schedule" | ||
if config.has_option(cfg, 'duration'): | ||
duration = config.get(cfg, 'duration') | ||
if config.has_option(cfg, 'note'): | ||
note = config.get(cfg, 'note') | ||
if config.has_option(cfg, 'ids'): | ||
ids = config.get(cfg, 'ids') | ||
|
||
now = datetime.datetime.now(tz=TZ) | ||
if args.duration: | ||
duration = args.duration | ||
|
||
try: | ||
end = now + datetime.timedelta(0,0,0,0,int(duration)) | ||
except Exception, e: | ||
print "Missing or invalid duration" | ||
sys.exit(1) | ||
|
||
if args.note: | ||
note = args.note | ||
|
||
if note == None: | ||
note="" | ||
|
||
if args.ids: | ||
ids = args.ids | ||
|
||
if not ids: | ||
print "Missing ID list" | ||
sys.exit(1) | ||
|
||
try: | ||
res = pager.maintenance_windows.create(start_time=now.isoformat(), | ||
end_time=end.isoformat(), | ||
service_ids=ids.split(","), | ||
requester_id=userid, | ||
description=note) | ||
except Exception, e: | ||
print "Error submitting maintenance window request" | ||
print e | ||
sys.exit(1) | ||
|
||
print_window(res) | ||
|
||
def do_delete(pager, args, config): | ||
if not args.option: | ||
print "Missing ID to delete" | ||
sys.exit(1) | ||
|
||
try: | ||
res = pager.maintenance_windows.delete(args.option) | ||
except Exception, e: | ||
print e | ||
sys.exit(1) | ||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser(description="PagerDuty Maintenance utility") | ||
parser.add_argument('-c', '--config', | ||
help="Configuration file. Default: ~/.pdmaint", | ||
required=False, | ||
default='~/.pdmaint') | ||
parser.add_argument('-d', '--duration', | ||
help="Duration in minutes of the scheduled downtime", | ||
required=False) | ||
parser.add_argument('-n', '--note', | ||
help="Optional note associated with the maintenance window", | ||
required=False) | ||
parser.add_argument('-i', '--ids', | ||
help="List of service ID's. Use 'services' command for a list of valid ID's", | ||
required=False) | ||
parser.add_argument('cmd', help="One of: services, users, schedule, list, delete, schedules") | ||
parser.add_argument('option', nargs='?', help="When cmd=schedule this can be an optional pre-defined schedule read from the configuration file. When cmd=delete this must contain the ID of the maintenance window to delete.") | ||
|
||
args = parser.parse_args() | ||
|
||
config = ConfigParser.ConfigParser() | ||
configfile = os.path.realpath(os.path.expanduser(args.config)) | ||
|
||
if os.path.isfile(configfile): | ||
try: | ||
config.read([configfile]) | ||
except Exception, e: | ||
print e | ||
sys.exit(1) | ||
else: | ||
print "File %s does not exist" % (configfile) | ||
sys.exit(1) | ||
|
||
# The conf file should look something like this: | ||
# [pagerduty] | ||
# api_key=abcdefghijklmnopqrst | ||
# email=someuser@somedomain.com | ||
# account=pagerduty_account_key | ||
# timezone=US/Eastern | ||
|
||
for p in [ 'api_key', 'email', 'account', 'timezone' ]: | ||
if not config.has_option('pagerduty', p): | ||
print "'%s' not defined pagerduty section of %s" % (p, args.config) | ||
sys.exit(1) | ||
|
||
api_key = config.get('pagerduty', 'api_key') | ||
email = config.get('pagerduty', 'email') | ||
account = config.get('pagerduty', 'account') | ||
TZ = timezone(config.get('pagerduty', 'timezone')) | ||
|
||
pager = pygerduty.PagerDuty(account, api_key) | ||
|
||
if args.cmd == 'services': | ||
do_services(pager, args, config) | ||
elif args.cmd == 'list': | ||
do_list(pager, args, config) | ||
elif args.cmd == 'users': | ||
do_users(pager, args, config) | ||
elif args.cmd == 'schedule': | ||
do_schedule(pager, args, config) | ||
elif args.cmd == 'schedules': | ||
do_schedules(pager, args, config) | ||
elif args.cmd == 'delete': | ||
do_delete(pager, args, config) | ||
else: | ||
print "Unknown command: %s" % (args.cmd) | ||
sys.exit(1) |
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,34 @@ | ||
# Copy this file to ~/.pdmaint and edit as necessary. All the entries in the | ||
# # [pagerduty] section are required. All subsequent sections with a _schedule | ||
# prefix are optional. | ||
|
||
[pagerduty] | ||
# PagerDuty API key. To obtain a key visit https://<your_account>.pagerduty.com/api_keys | ||
api_key=xxxxxxxxxxxxxxxxxxxx | ||
|
||
# e-mail address associated with a PagerDuty account | ||
email=you@yourdomain.com | ||
|
||
# The name of your account. From: https://your_account.pagerduty.com/ | ||
account=your_account | ||
|
||
# Your local timezone. The tzinfo utility can be used to see all valid values. | ||
timezone=US/Eastern | ||
|
||
|
||
# The following ar eexamples of defined schedules. Use the command 'pdmaint services' | ||
# to obtain a list of service ID's. | ||
|
||
# Schedule 2 hours of downtime when performing a release. Disable a bunch of | ||
# service IDs | ||
[release_schedule] | ||
duration=120 | ||
note=Production release | ||
ids=XYZ1234,A1B2C3D,XXXXXXX,YYYYYYY | ||
|
||
# Schedule a 30 minute maintenance window for one service | ||
[maintenance_schedule] | ||
duration=30 | ||
note=maintenance window | ||
ids=DK3X403 | ||
|
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,6 @@ | ||
#!/usr/bin/env python | ||
|
||
import pytz | ||
|
||
for tz in pytz.all_timezones: | ||
print tz |