From 094e46cda0204193996e248f365e5ae7a727decf Mon Sep 17 00:00:00 2001 From: jjinno Date: Tue, 21 May 2019 16:22:10 -0700 Subject: [PATCH 1/9] Update docs for Events API v2 --- README.rst | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 0649c1e..767aac6 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ Pygerduty ========= Python Library for PagerDuty's REST API and Events API. This library was originally written to support v1 and -is currently being updated to be compatible with v2 of the API. See "Migrating from v1 to v2" for more details. +is currently being updated to be compatible with v2 of the API. See "Migrating REST API from v1 to v2" for more details. This library is currently evolving and backwards compatibility cannot always be guaranteed at this time. @@ -42,35 +42,51 @@ see all available endpoints. Top level resources will be accessible via the PagerDuty object and nested resources available on containers returned from their parent resource. +For documentation on the Events API v2, please refer to the `Events API v2 +Documentation `_ to +see all requirements. Additionally refer to the `Common Events Format (PD-CEF) +Documentation `_ to understand +the new fields, both optional and required. -Migrating from v1 to v2 -======================= +Migrating REST API from v1 to v2 +================================ -In order to allow for a smooth transition between versions 1 and 2 of the library, -version 1 library remains in the file called `__init__.py` inside of the pygerduty directory. +In order to allow for a smooth transition between versions 1 and 2 of the Pygerduty library, +version 1 library remains in the file called ``__init__.py`` inside of the pygerduty directory. Also in that directory you will see four other files: -- `v2.py` — This file contains all updated logic compatible with v2 of the API. -- `events.py` — PygerDuty also provides an Events API which is separate from the REST API that has had the recent update. Since the logic is mostly disjoint, we have created a new module for logic related to the Events API. -- `common.py` — This file contains all common functions used by both `v2.py` and `events.py`. -- `version.py` — Contains version info. +- ``v2.py`` — This file contains all updated logic compatible with v2 of the API. +- ``events.py`` — PygerDuty also provides an Events API which is separate from the REST API that has had the recent update. Since the logic is mostly disjoint, we have created a new module for logic related to the Events API. +- ``common.py`` — This file contains all common functions used by both `v2.py` and `events.py`. +- ``version.py`` — Contains version info. See the examples below to see how this affects how you will instantiate a client in v2. +Migrating Events API from v1 to v2 +================================== + +In order to allow for a smooth transition between versions 1 and 2 of the Events API, +the version 1 class remains in the file called ``events.py`` inside of the pygerduty directory. +Also in that directory you will see a new file: + +- ``events_v2.py`` — This file contains all updated logic compatible with the Events API v2. + +Although the library still only contains one `Events` class, and that class still provides ``trigger_incident``, ``acknowledge_incident``, and ``resolve_incident`` methods, the fields required by PagerDuty have changed. Please take a moment to reference their `Events API v2 Documentation `_ and their explanation of their new `Common Events Format (PD-CEF) Documentation `_ + Examples ======== Instantiating a client: -Version 1: +REST API Version 1: :: import pygerduty pager = pygerduty.PagerDuty("foobar", "SOMEAPIKEY123456") -Version 2: +REST API Version 2: :: From a3dc8d82ac856e6431ea82190f943e593630cc04 Mon Sep 17 00:00:00 2001 From: jjinno Date: Tue, 21 May 2019 16:24:41 -0700 Subject: [PATCH 2/9] Create events_v2.py Created Events class compatible with Events API v2 (not to be confused with REST API v2) New API comes with new documented required fields and new optional fields. Documentation links provided at top of code for reference to the PD-CEF & Events API v2 Implemented enqueue() to allow for payload to be specified by caller. This allows direct use of the JSON produced for the request (better for unit-testing enqueue function) Note that summary/source/severity are still required, and throw a KeyError if not found in either kwargs or payload. --- pygerduty/events_v2.py | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 pygerduty/events_v2.py diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py new file mode 100644 index 0000000..3372127 --- /dev/null +++ b/pygerduty/events_v2.py @@ -0,0 +1,81 @@ +# These methods are compatible with Pagerduty Events API v2 +# - https://v2.developer.pagerduty.com/docs/events-api-v2 +# - https://support.pagerduty.com/docs/pd-cef + +from six.moves import urllib +from .exceptions import Error, IntegrationAPIError, BadRequest, NotFound +from .common import ( + _json_dumper, + Requester, +) + + +INTEGRATION_API_URL = "https://events.pagerduty.com/v2/enqueue" + + +class Events(object): + def __init__(self, routing_key, requester=None): + self.routing_key = routing_key + self.headers = { + "Content-type": "application/json", + "Accept": "application/vnd.pagerduty+json;version=2", + "X-Routing-Key": self.routing_key + } + if requester is None: + self.requester = Requester() + else: + self.requester = requester + + def enqueue(self, **kwargs): + # Required (according to documentation) PD-CEF fields + data = {"event_action": kwargs['event_action']} + data['payload'] = kwargs.get('payload', {}) + for key in ['summary','source','severity']: + if key in kwargs: + data['payload'][key] = kwargs[key] + elif key not in data['payload']: + raise KeyError(key) + + # Optional event fields + for key in ['dedup_key','routing_key']: + if key in kwargs.keys(): + data[key] = kwargs[key] + + # Optional PD-CEF fields + for key in ['component','group','class','links','images', + 'custom_details','timestamp']: + if key in kwargs.keys(): + data['payload'][key] = kwargs[key] + + request = urllib.request.Request(INTEGRATION_API_URL, + data=_json_dumper(data).encode('utf-8'), + headers=self.headers) + response = self.requester.execute_request(request) + + if not response.get("status", "failure") == "success": + raise IntegrationAPIError(response["message"], event_action) + return response["dedup_key"] + + def resolve_incident(self, **kwargs): + """ Causes the referenced incident to enter resolved state. + Send a resolve event when the problem that caused the initial + trigger has been fixed. + """ + kwargs['event_action'] = 'resolve' + return self.enqueue(**kwargs) + + def acknowledge_incident(self, **kwargs): + """ Causes the referenced incident to enter the acknowledged state. + Send an acknowledge event when someone is presently working on the + incident. + """ + kwargs['event_action'] = 'acknowledge' + return self.enqueue(**kwargs) + + def trigger_incident(self, **kwargs): + """ Report a new or ongoing problem. When PagerDuty receives a trigger, + it will either open a new incident, or add a new log entry to an + existing incident. + """ + kwargs['event_action'] = 'trigger' + return self.enqueue(**kwargs) From a20c60689cc59672d106584806c3ce4a878b1967 Mon Sep 17 00:00:00 2001 From: jjinno Date: Tue, 21 May 2019 16:27:27 -0700 Subject: [PATCH 3/9] Create event_request_v2.json --- tests/fixtures/event_request_v2.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/fixtures/event_request_v2.json diff --git a/tests/fixtures/event_request_v2.json b/tests/fixtures/event_request_v2.json new file mode 100644 index 0000000..621984f --- /dev/null +++ b/tests/fixtures/event_request_v2.json @@ -0,0 +1,17 @@ +{ + "event_action": "trigger", + "payload": { + "custom_details": { + "Service Name": "Test Service", + "Problem": "Something explanation", + "Service ID": "P123456", + "Result": "Error testing occurred" + }, + "severity": "error", + "component": "Test Component", + "summary": "Service (P123456) Test Error", + "source": "Test Source", + "class": "Unit Test" + }, + "dedup_key": "Service (P123456) Test Dedup Key" +} From c4bb0daa0dd1f4092635cfdf25cd25854e38e95e Mon Sep 17 00:00:00 2001 From: jjinno Date: Tue, 21 May 2019 16:29:13 -0700 Subject: [PATCH 4/9] Add Events API v2 test Similar to v1 test: directly tests the enqueue() function with a built-in event_action of "trigger" --- tests/events_test.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/events_test.py b/tests/events_test.py index 790bcb6..d920a31 100644 --- a/tests/events_test.py +++ b/tests/events_test.py @@ -2,8 +2,10 @@ import json import textwrap import pygerduty.events +import pygerduty.events_v2 from pygerduty.events import INTEGRATION_API_URL +from pygerduty.events_v2 import INTEGRATION_API_URL as INTEGRATION_API_URL_V2 from pygerduty.common import Requester @@ -35,3 +37,23 @@ def test_create_event(): assert response == 'srv01/HTTP' + +@httpretty.activate +def test_enqueue_event_v2(): + response = { + "status": "success", + "message": "Event processed", + "dedup_key": "Service (P123456) Test Dedup Key" + } + httpretty.register_uri( + httpretty.POST, INTEGRATION_API_URL_V2, + body=textwrap.dedent(json.dumps(response)), status=200) + + E = pygerduty.events_v2.Events('fake_integration_key') + + request = {} + with open('tests/fixtures/event_request_v2.json') as fp: + request = json.load(fp) + + dedup_key = E.enqueue(**request) + assert dedup_key == response['dedup_key'] From 554742c4d86a0392f9c8de556106c895c8fabd6d Mon Sep 17 00:00:00 2001 From: jjinno Date: Tue, 21 May 2019 16:46:30 -0700 Subject: [PATCH 5/9] Fixed flake8 bugs --- pygerduty/events_v2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py index 3372127..762dbbb 100644 --- a/pygerduty/events_v2.py +++ b/pygerduty/events_v2.py @@ -30,20 +30,20 @@ def enqueue(self, **kwargs): # Required (according to documentation) PD-CEF fields data = {"event_action": kwargs['event_action']} data['payload'] = kwargs.get('payload', {}) - for key in ['summary','source','severity']: + for key in ['summary', 'source', 'severity']: if key in kwargs: data['payload'][key] = kwargs[key] elif key not in data['payload']: raise KeyError(key) # Optional event fields - for key in ['dedup_key','routing_key']: + for key in ['dedup_key', 'routing_key']: if key in kwargs.keys(): data[key] = kwargs[key] # Optional PD-CEF fields - for key in ['component','group','class','links','images', - 'custom_details','timestamp']: + for key in ['component', 'group', 'class', 'links', 'images', + 'custom_details', 'timestamp']: if key in kwargs.keys(): data['payload'][key] = kwargs[key] @@ -53,9 +53,9 @@ def enqueue(self, **kwargs): response = self.requester.execute_request(request) if not response.get("status", "failure") == "success": - raise IntegrationAPIError(response["message"], event_action) + raise IntegrationAPIError(response["message"], kwargs['event_action']) return response["dedup_key"] - + def resolve_incident(self, **kwargs): """ Causes the referenced incident to enter resolved state. Send a resolve event when the problem that caused the initial From 6e7e1c8ade7208baa6db06d65ccb7956f9976052 Mon Sep 17 00:00:00 2001 From: jjinno Date: Thu, 20 Jun 2019 12:00:32 -0700 Subject: [PATCH 6/9] moved images/links out of payload https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 Shows 'images' and 'links' outside of payload. --- pygerduty/events_v2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py index 762dbbb..34f4538 100644 --- a/pygerduty/events_v2.py +++ b/pygerduty/events_v2.py @@ -37,12 +37,12 @@ def enqueue(self, **kwargs): raise KeyError(key) # Optional event fields - for key in ['dedup_key', 'routing_key']: + for key in ['dedup_key', 'routing_key', 'images', 'links']: if key in kwargs.keys(): data[key] = kwargs[key] # Optional PD-CEF fields - for key in ['component', 'group', 'class', 'links', 'images', + for key in ['component', 'group', 'class', 'custom_details', 'timestamp']: if key in kwargs.keys(): data['payload'][key] = kwargs[key] From f544cb00be919a69f5482fc519eb4be611a6b9ff Mon Sep 17 00:00:00 2001 From: jjinno Date: Thu, 5 Dec 2019 12:11:57 -0800 Subject: [PATCH 7/9] Made payload optional Even though payload is now optional, if it is specified, the fields within it are still checked as required --- pygerduty/events_v2.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py index 34f4538..a2b6458 100644 --- a/pygerduty/events_v2.py +++ b/pygerduty/events_v2.py @@ -29,24 +29,36 @@ def __init__(self, routing_key, requester=None): def enqueue(self, **kwargs): # Required (according to documentation) PD-CEF fields data = {"event_action": kwargs['event_action']} - data['payload'] = kwargs.get('payload', {}) - for key in ['summary', 'source', 'severity']: + + # Determine if a 'payload' is required + payload_required = False + for key in ['payload', 'summary', 'source', 'severity', + 'component', 'group', 'class', 'custom_details', 'timestamp']: if key in kwargs: - data['payload'][key] = kwargs[key] - elif key not in data['payload']: - raise KeyError(key) + payload_required = True + + # Build optional payload from kwargs + if payload_required: + data['payload'] = kwargs.get('payload', {}) + + # Required payload fields + for key in ['summary', 'source', 'severity']: + if key in kwargs: + data['payload'][key] = kwargs[key] + elif key not in data['payload']: + raise KeyError(key) + + # Optional payload fields + for key in ['component', 'group', 'class', + 'custom_details', 'timestamp']: + if key in kwargs: + data['payload'][key] = kwargs[key] # Optional event fields for key in ['dedup_key', 'routing_key', 'images', 'links']: - if key in kwargs.keys(): + if key in kwargs: data[key] = kwargs[key] - # Optional PD-CEF fields - for key in ['component', 'group', 'class', - 'custom_details', 'timestamp']: - if key in kwargs.keys(): - data['payload'][key] = kwargs[key] - request = urllib.request.Request(INTEGRATION_API_URL, data=_json_dumper(data).encode('utf-8'), headers=self.headers) From 9d25ada7cf86bf4a9e3bb110fead61a39921863c Mon Sep 17 00:00:00 2001 From: jjinno Date: Thu, 5 Dec 2019 13:30:41 -0800 Subject: [PATCH 8/9] Fixed flake8 issues --- pygerduty/events_v2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py index a2b6458..b6b8c7e 100644 --- a/pygerduty/events_v2.py +++ b/pygerduty/events_v2.py @@ -33,7 +33,8 @@ def enqueue(self, **kwargs): # Determine if a 'payload' is required payload_required = False for key in ['payload', 'summary', 'source', 'severity', - 'component', 'group', 'class', 'custom_details', 'timestamp']: + 'component', 'group', 'class', 'custom_details', + 'timestamp']: if key in kwargs: payload_required = True @@ -50,7 +51,7 @@ def enqueue(self, **kwargs): # Optional payload fields for key in ['component', 'group', 'class', - 'custom_details', 'timestamp']: + 'custom_details', 'timestamp']: if key in kwargs: data['payload'][key] = kwargs[key] From b77b824aea2257bc95c7b15e673b84db7d04855c Mon Sep 17 00:00:00 2001 From: jjinno Date: Thu, 5 Dec 2019 13:33:39 -0800 Subject: [PATCH 9/9] fixed whitespace issue --- pygerduty/events_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygerduty/events_v2.py b/pygerduty/events_v2.py index b6b8c7e..60069cb 100644 --- a/pygerduty/events_v2.py +++ b/pygerduty/events_v2.py @@ -29,7 +29,7 @@ def __init__(self, routing_key, requester=None): def enqueue(self, **kwargs): # Required (according to documentation) PD-CEF fields data = {"event_action": kwargs['event_action']} - + # Determine if a 'payload' is required payload_required = False for key in ['payload', 'summary', 'source', 'severity',