Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features: add title filtering and hourly slicing when date range is specified #41

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
14 changes: 11 additions & 3 deletions mass_update_incidents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

Performs status updates (acknowledge or resolve) in bulk to an almost arbitrary
number (maximum: 10k at a time) of incidents that all have an assignee user or
service (or both) in commmon.
service (or both) in commmon.

If operating on more than 10k incidents: it is recommended that you run the
script several times by constraining it to a service ID each time, and/or
requesting a time range by setting the `-d/--date-range` option.
script several times by constraining it to a service ID each time.

Alternatively, specify a time range by setting the `-d/--date-range` option. If
the difference between the beginning and end times is longer than 1 hour, the
script will run in batches of 1 hour across the time range. This is intended
to help keep each batch size within 10k incidents.

Errors returned by the REST API are reported to stderr but do not interrupt the
script's execution, to ensure that all incidents in scope are tried at least
once each.
99 changes: 73 additions & 26 deletions mass_update_incidents/mass_update_incidents.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/env python
#!/usr/bin/env python3

# PagerDuty Support asset: mass_update_incidents

import argparse
import requests
import sys
import json
from datetime import date
from datetime import date, datetime, timedelta
from dateutil.parser import parse, ParserError
import pprint

import pdpyras
Expand All @@ -22,6 +23,7 @@
def mass_update_incidents(args):
session = pdpyras.APISession(args.api_key,
default_from=args.requester_email)
hour_slices = []
if args.user_id:
PARAMETERS['user_ids[]'] = args.user_id.split(',')
print("Acting on incidents assigned to user(s): "+args.user_id)
Expand All @@ -35,42 +37,84 @@ def mass_update_incidents(args):
elif args.action == 'acknowledge':
PARAMETERS['statuses[]'] = ['triggered']
print("Acknowledging incidents")
if args.date_range is not None:
if args.date_range is None:
PARAMETERS['date_range'] = 'all'
print("Getting incidents of all time")
else:
sinceuntil = args.date_range.split(',')
if len(sinceuntil) != 2:
raise ValueError("Date range must be two ISO8601-formatted time "
"stamps separated by a comma.")
PARAMETERS['since'] = sinceuntil[0]
PARAMETERS['until'] = sinceuntil[1]
given_start_date = parse(sinceuntil[0])
given_end_date = parse(sinceuntil[1])
if given_end_date - given_start_date > timedelta(hours=1):
hour_slices = hour_slicer(sinceuntil[0], sinceuntil[1])
# else:
# PARAMETERS['since'] = sinceuntil[0]
# PARAMETERS['until'] = sinceuntil[1]
print("Getting incidents for date range: "+" to ".join(sinceuntil))
else:
PARAMETERS['date_range'] = 'all'
print("Getting incidents of all time")
print("Parameters: "+str(PARAMETERS))
try:
print("Please be patient as this can take a while for large volumes "
"of incidents.")
for incident in session.list_all('incidents', params=PARAMETERS):
print("Please be patient as this can take a while for large volumes "
"of incidents.")
# if 1hr slicing is not needed, then this for loop will be no-op
for begin_date in hour_slices:
print("Now retrieving a batch of incidents...")
PARAMETERS['since'] = begin_date
PARAMETERS['until'] = begin_date + timedelta(hours=1)
incident_handler(session, args)
# if sliced into hours, this performs one final sweep through the whole
# range to catch stragglers
incident_handler(session, args)

def hour_slicer(begin_time, end_time):
date_range = []
start_time = parse(begin_time)
end_time = parse(end_time)
while start_time < end_time:
date_range.append(start_time)
start_time = start_time + timedelta(hours=1)
date_range.append(start_time)
return date_range

def incident_handler(session, args):
for incident in session.list_all('incidents', params=PARAMETERS):
if args.dry_run:
print("* Incident {}: {}".format(incident['id'], args.action))
if args.dry_run:
continue
session.rput(incident['self'], json={
'type': 'incident_reference',
'id': incident['id'],
'status': '{0}d'.format(args.action), # acknowledged or resolved
})
except pdpyras.PDClientError as e:
if e.response is not None:
print(e.response.text)
raise e
continue
if args.title_filter is None:
try:
print("* Incident {}: {}".format(incident['id'], args.action))
session.rput(incident['self'], json={
'type': 'incident_reference',
'id': incident['id'],
'status': '{0}d'.format(args.action),
})
except pdpyras.PDClientError as e:
if e.response is not None:
print(e.response.text)
# raise e
else:
if 'title' in incident.keys() and \
args.title_filter in incident['title']:
print("* Incident {}: {}".format(incident['id'], args.action))
try:
session.rput(incident['self'], json={
'type': 'incident_reference',
'id': incident['id'],
'status': '{0}d'.format(args.action),
})
except pdpyras.PDClientError as e:
if e.response is not None:
print(e.response.text)
# raise e

def main(argv=None):
ap = argparse.ArgumentParser(description="Mass ack or resolve incidents "
"either corresponding to a given service, or assigned to a given "
"user. Note, if you are trying to update over 10k incidents at a "
"time, you should set the --date-range argument to a lesser interval "
"of time and then run this script multiple times with a different "
"interval each time until the desired range of time is covered.")
"time, you should use the --date-range argument instead. If the date "
"range given is longer than 1 hour it will be batched into 1-hour "
" slices.")
ap.add_argument('-d', '--date-range', default=None, help="Only act on "
"incidents within a date range. Must be a pair of ISO8601-formatted "
"time stamps, separated by a comma, representing the beginning (since) "
Expand All @@ -89,6 +133,9 @@ def main(argv=None):
'resolve'], help="Action to take on incidents en masse")
ap.add_argument('-e', '--requester-email', required=True, help="Email "
"address of the user who will be marked as performing the actions.")
ap.add_argument('-t', '--title-filter', default=None, help="(Optional) "
"string to search in the Title field of each Incident, to ensure only "
"known types of incidents are handled.")
args = ap.parse_args()
mass_update_incidents(args)

Expand Down
2 changes: 2 additions & 0 deletions mass_update_incidents/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pdpyras >= 2.0.0
python-dateutil
datetime