From fb84b3427b194027d3c40fa727ba455109a661a8 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Tue, 27 Mar 2018 13:30:28 +1100 Subject: [PATCH 1/9] first successful cut --- bin/pd-send | 15 ++++++++++++++- pdagent/constants.py | 2 +- pdagent/pdagentutil.py | 23 +++++++++++++++-------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index 6a7856b6..aa63563b 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -46,6 +46,15 @@ def build_queue_arg_parser(description): "-d", "--description", dest="description", help="Short description of the problem" ) + parser.add_argument( + "-src", "--source", dest="source", + help="Source of issue" + ) + parser.add_argument( + "-s", "--severity", dest="severity", + choices=["critical", "warning", "error","info"], + help="Severity value for the problem ('critical', 'warning', 'error' or 'info')" + ) parser.add_argument( "-i", "--incident-key", dest="incident_key", help="Incident Key" @@ -86,6 +95,10 @@ def main(): if args.event_type == "trigger": if (not args.description) or (not args.description.strip()): parser.error("Event type '%s' requires description" % args.event_type) + if (not args.severity) or ( not args.severity.strip()): + parser.error("Event type '%s' requires severity" % args.event_type) + if (not args.source) or ( not args.source.strip()): + parser.error("Event type '%s' requires origin" % args.event_type) else: if not args.incident_key: parser.error("Event type '%s' requires incident key" % args.event_type) @@ -95,7 +108,7 @@ def main(): enqueuer = agent_config.get_enqueuer() incident_key, problems = queue_event( enqueuer, - args.event_type, args.service_key, args.incident_key, args.description, + args.event_type, args.severity, args.source,args.service_key, args.incident_key, args.description, args.client, args.client_url, details, agent_config.get_agent_id(), "pd-send", ) diff --git a/pdagent/constants.py b/pdagent/constants.py index cedf906a..3f462c39 100644 --- a/pdagent/constants.py +++ b/pdagent/constants.py @@ -45,7 +45,7 @@ # PD event integration API. EVENTS_API_BASE = \ - "https://events.pagerduty.com/generic/2010-04-15/create_event.json" + "https://events.pagerduty.com/v2/enqueue" # PD heartbeat end-point. HEARTBEAT_URI = "https://api.pagerduty.com/agent/2014-03-14/heartbeat" diff --git a/pdagent/pdagentutil.py b/pdagent/pdagentutil.py index 4c7c0304..5eb68b84 100644 --- a/pdagent/pdagentutil.py +++ b/pdagent/pdagentutil.py @@ -68,7 +68,7 @@ def utcnow_isoformat(time_calc=None): def queue_event( enqueuer, - event_type, service_key, incident_key, description, client, client_url, details, + event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, agent_id, queued_by, ): agent_context = { @@ -77,7 +77,7 @@ def queue_event( "queued_at": utcnow_isoformat() } event = _build_event_json_str( - event_type, service_key, incident_key, description, client, client_url, details, + event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, agent_context ) _, problems = enqueuer.enqueue(service_key, event) @@ -97,18 +97,25 @@ def get_stats(queue, service_key): def _build_event_json_str( - event_type, service_key, incident_key, description, client, client_url, details, + event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, agent_context=None ): + p = { + "custom_details": details + } d = { - "service_key": service_key, - "event_type": event_type, - "details": details, + "payload": p, + "routing_key": service_key, + "event_action": event_type } if incident_key is not None: - d["incident_key"] = incident_key + d["dedup_key"] = incident_key if description is not None: - d["description"] = description + p["summary"] = description + if event_source is not None: + p["source"] = event_source + if event_severity is not None: + p["severity"] = event_severity if client is not None: d["client"] = client if client_url is not None: From 8fdc8a90a26ca0412b0f6928b71c59aa138f6d6a Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Wed, 28 Mar 2018 11:40:40 +1100 Subject: [PATCH 2/9] final change for branch --- bin/pd-send | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pd-send b/bin/pd-send index aa63563b..4fea27d2 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -53,7 +53,7 @@ def build_queue_arg_parser(description): parser.add_argument( "-s", "--severity", dest="severity", choices=["critical", "warning", "error","info"], - help="Severity value for the problem ('critical', 'warning', 'error' or 'info')" + help="Severity value for the problem" ) parser.add_argument( "-i", "--incident-key", dest="incident_key", From 5f88d0646d6f7fd9e1af4977d7b86259d266d87f Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Tue, 3 Apr 2018 11:10:26 +1000 Subject: [PATCH 3/9] Update for extra fields (kept input items same for old items so no breakages of old scripts) --- bin/pd-send | 27 ++++++++++++++++++++------- pdagent/pdagentutil.py | 21 ++++++++++++++------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index 4fea27d2..86196bb3 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -35,7 +35,7 @@ def build_queue_arg_parser(description): parser = ArgumentParser(description=description) parser.add_argument( "-k", "--service-key", dest="service_key", required=True, - help="Service API Key" + help="Service API Key/Routing key" ) parser.add_argument( "-t", "--event-type", dest="event_type", required=True, @@ -44,20 +44,32 @@ def build_queue_arg_parser(description): ) parser.add_argument( "-d", "--description", dest="description", - help="Short description of the problem" + help="(Payload)Summary of the problem" ) parser.add_argument( "-src", "--source", dest="source", - help="Source of issue" + help="(Payload)Source of issue" ) parser.add_argument( "-s", "--severity", dest="severity", choices=["critical", "warning", "error","info"], - help="Severity value for the problem" + help="(Payload)Severity value for the problem" + ) + parser.add_argument( + "-c", "--component", dest="component", + help="(Payload)Component value for the problem" + ) + parser.add_argument( + "-g", "--group", dest="group", + help="(Payload)Group value for the problem" + ) + parser.add_argument( + "-cls", "--class", dest="prob_class", + help="(Payload)Class value for the problem" ) parser.add_argument( "-i", "--incident-key", dest="incident_key", - help="Incident Key" + help="Incident Key/Dedup key" ) parser.add_argument( "-c", "--client", dest="client", @@ -69,7 +81,7 @@ def build_queue_arg_parser(description): ) parser.add_argument( "-f", "--field", action="append", dest="fields", - help="Add given KEY=VALUE pair to the event details" + help="Add given KEY=VALUE pair to the (Payload)custom details" ) parser.add_argument( "-q", "--quiet", action="store_true", dest="quiet", @@ -108,7 +120,8 @@ def main(): enqueuer = agent_config.get_enqueuer() incident_key, problems = queue_event( enqueuer, - args.event_type, args.severity, args.source,args.service_key, args.incident_key, args.description, + args.event_type, args.severity, args.source, args.component, args.group, args.prob_class, + args.service_key, args.incident_key, args.description, args.client, args.client_url, details, agent_config.get_agent_id(), "pd-send", ) diff --git a/pdagent/pdagentutil.py b/pdagent/pdagentutil.py index 5eb68b84..22edebef 100644 --- a/pdagent/pdagentutil.py +++ b/pdagent/pdagentutil.py @@ -68,8 +68,8 @@ def utcnow_isoformat(time_calc=None): def queue_event( enqueuer, - event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, - agent_id, queued_by, + event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, + description, client, client_url, details, agent_id, queued_by, ): agent_context = { "agent_id": agent_id, @@ -77,8 +77,8 @@ def queue_event( "queued_at": utcnow_isoformat() } event = _build_event_json_str( - event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, - agent_context + event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, + description, client, client_url, details, agent_context ) _, problems = enqueuer.enqueue(service_key, event) return incident_key, problems @@ -97,9 +97,10 @@ def get_stats(queue, service_key): def _build_event_json_str( - event_type, event_severity, event_source, service_key, incident_key, description, client, client_url, details, - agent_context=None - ): + event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, + description, client, client_url, details, + agent_context=None + ): p = { "custom_details": details } @@ -114,6 +115,12 @@ def _build_event_json_str( p["summary"] = description if event_source is not None: p["source"] = event_source + if event_component is not None: + p["component"] = event_component + if event_group is not None: + p["group"] = event_group + if event_class is not None: + p["class"] = event_class if event_severity is not None: p["severity"] = event_severity if client is not None: From 9672abcec0f26980852e7596c4144e276fac9ad1 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Tue, 3 Apr 2018 13:42:27 +1000 Subject: [PATCH 4/9] fix for collision of params --- bin/pd-send | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pd-send b/bin/pd-send index 86196bb3..11833851 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -56,7 +56,7 @@ def build_queue_arg_parser(description): help="(Payload)Severity value for the problem" ) parser.add_argument( - "-c", "--component", dest="component", + "-cmp", "--component", dest="component", help="(Payload)Component value for the problem" ) parser.add_argument( From e29b8edd6e28b3d9f906035f91e0b34020eb6682 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Thu, 5 Apr 2018 11:09:36 +1000 Subject: [PATCH 5/9] Updates to support V1 and V2 api (basically route by message content - service_key for V1 and routing_key for V2 (checks for presence to delegate to right endpoint) --- bin/pd-send | 7 ++++++- pdagent/constants.py | 13 +++++++++++-- pdagent/pdagentutil.py | 44 ++++++++++++++++++++++++++++++++++++------ pdagent/sendevent.py | 14 ++++++++++++-- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index 11833851..f0128fba 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -37,6 +37,11 @@ def build_queue_arg_parser(description): "-k", "--service-key", dest="service_key", required=True, help="Service API Key/Routing key" ) + parser.add_argument( + "-api", "--apiVersion", dest="api_version", + choices=["V1", "V2",], default="V1", + help="API version to target" + ) parser.add_argument( "-t", "--event-type", dest="event_type", required=True, choices=["trigger", "acknowledge", "resolve"], @@ -119,7 +124,7 @@ def main(): enqueuer = agent_config.get_enqueuer() incident_key, problems = queue_event( - enqueuer, + enqueuer, args.api_version, args.event_type, args.severity, args.source, args.component, args.group, args.prob_class, args.service_key, args.incident_key, args.description, args.client, args.client_url, details, diff --git a/pdagent/constants.py b/pdagent/constants.py index 3f462c39..3a223ae4 100644 --- a/pdagent/constants.py +++ b/pdagent/constants.py @@ -43,9 +43,18 @@ # PDEnqueue warnings. EnqueueWarnings = enum('UMASK_TOO_RESTRICTIVE') -# PD event integration API. -EVENTS_API_BASE = \ +# PD event integration API V1. +EVENTS_API_BASE_V2 = \ "https://events.pagerduty.com/v2/enqueue" +# PD event service +SERVICE_KEY_KEY_V2 = "routing_key" + +# PD event integration API V2. +EVENTS_API_BASE_V1 = "https://events.pagerduty.com/generic/2010-04-15/create_event.json" + +# PD event service +SERVICE_KEY_KEY_V1 = "service_key" + # PD heartbeat end-point. HEARTBEAT_URI = "https://api.pagerduty.com/agent/2014-03-14/heartbeat" diff --git a/pdagent/pdagentutil.py b/pdagent/pdagentutil.py index 22edebef..5d8dff02 100644 --- a/pdagent/pdagentutil.py +++ b/pdagent/pdagentutil.py @@ -67,7 +67,7 @@ def utcnow_isoformat(time_calc=None): return time_calc.strftime("%Y-%m-%dT%H:%M:%SZ", time_calc.gmtime()) def queue_event( - enqueuer, + enqueuer, api_version, event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, description, client, client_url, details, agent_id, queued_by, ): @@ -76,10 +76,15 @@ def queue_event( "queued_by": queued_by, "queued_at": utcnow_isoformat() } - event = _build_event_json_str( - event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, - description, client, client_url, details, agent_context - ) + if api_version=="V1": + event = _build_event_json_str_V1( + event_type, service_key, incident_key, description, client, client_url, details, + ) + else: + event = _build_event_json_str_V2( + event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, + description, client, client_url, details, agent_context + ) _, problems = enqueuer.enqueue(service_key, event) return incident_key, problems @@ -96,7 +101,7 @@ def get_stats(queue, service_key): ) -def _build_event_json_str( +def _build_event_json_str_V2( event_type, event_severity, event_source, event_component, event_group, event_class, service_key, incident_key, description, client, client_url, details, agent_context=None @@ -135,3 +140,30 @@ def _build_event_json_str( separators=(',', ':'), # compact json str sort_keys=True ) + + +def _build_event_json_str_V1( + event_type, service_key, incident_key, description, client, client_url, details, + agent_context=None + ): + d = { + "service_key": service_key, + "event_type": event_type, + "details": details, + } + if incident_key is not None: + d["incident_key"] = incident_key + if description is not None: + d["description"] = description + if client is not None: + d["client"] = client + if client_url is not None: + d["client_url"] = client_url + if agent_context is not None: + d["agent"] = agent_context + + return json.dumps( + d, + separators=(',', ':'), # compact json str + sort_keys=True + ) \ No newline at end of file diff --git a/pdagent/sendevent.py b/pdagent/sendevent.py index 70d2c2ec..abb1d93d 100644 --- a/pdagent/sendevent.py +++ b/pdagent/sendevent.py @@ -35,7 +35,7 @@ from pdagent.thirdparty import httpswithverify from pdagent.thirdparty.ssl_match_hostname import CertificateError -from pdagent.constants import ConsumeEvent, EVENTS_API_BASE +from pdagent.constants import ConsumeEvent, EVENTS_API_BASE_V1, SERVICE_KEY_KEY_V1, EVENTS_API_BASE_V2, SERVICE_KEY_KEY_V2 from pdagent.pdqueue import EmptyQueueError from pdagent.pdthread import RepeatingTask @@ -84,7 +84,17 @@ def tick(self): def send_event(self, json_event_str, event_id): # Note that Request here is from urllib2, not self._urllib2. - request = Request(EVENTS_API_BASE) + # choose api version based on means of "" + if SERVICE_KEY_KEY_V1 in json_event_str: + logger.debug("Sending event to V1 Api: ") + request = Request(EVENTS_API_BASE_V1) + elif SERVICE_KEY_KEY_V2 in json_event_str: + logger.debug("Sending event to V2 Api: ") + request = Request(EVENTS_API_BASE_V2) + else: + logger.debug("Unknown service key sending to V1 api ") + request = Request(EVENTS_API_BASE_V1) + request.add_header("Content-type", "application/json") request.add_data(json_event_str) From 5f260796ead83a6ccd5ef057e3d794cf7ce8ce85 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Thu, 5 Apr 2018 11:46:01 +1000 Subject: [PATCH 6/9] Added more information on help (to show V2 only args) and added missing agent info append for V1 api. Also modified README.md to describe changes for this verison --- bin/pd-send | 10 +++++----- pdagent/pdagentutil.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index f0128fba..c396fb40 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -40,7 +40,7 @@ def build_queue_arg_parser(description): parser.add_argument( "-api", "--apiVersion", dest="api_version", choices=["V1", "V2",], default="V1", - help="API version to target" + help="API version to target (default is V1 if not set)" ) parser.add_argument( "-t", "--event-type", dest="event_type", required=True, @@ -58,19 +58,19 @@ def build_queue_arg_parser(description): parser.add_argument( "-s", "--severity", dest="severity", choices=["critical", "warning", "error","info"], - help="(Payload)Severity value for the problem" + help="(Payload)Severity value for the problem (-api V2 only)" ) parser.add_argument( "-cmp", "--component", dest="component", - help="(Payload)Component value for the problem" + help="(Payload)Component value for the problem (-api V2 only)" ) parser.add_argument( "-g", "--group", dest="group", - help="(Payload)Group value for the problem" + help="(Payload)Group value for the problem (-api V2 only)" ) parser.add_argument( "-cls", "--class", dest="prob_class", - help="(Payload)Class value for the problem" + help="(Payload)Class value for the problem (-api V2 only)" ) parser.add_argument( "-i", "--incident-key", dest="incident_key", diff --git a/pdagent/pdagentutil.py b/pdagent/pdagentutil.py index 5d8dff02..85a5c4b4 100644 --- a/pdagent/pdagentutil.py +++ b/pdagent/pdagentutil.py @@ -78,7 +78,7 @@ def queue_event( } if api_version=="V1": event = _build_event_json_str_V1( - event_type, service_key, incident_key, description, client, client_url, details, + event_type, service_key, incident_key, description, client, client_url, details, agent_context ) else: event = _build_event_json_str_V2( From 114becf6501c2e418d70483361a1cc5be6f5ad79 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Thu, 5 Apr 2018 12:01:26 +1000 Subject: [PATCH 7/9] Update to give info on help to inform if V2 only arg (ignored if using V1 option) Fixed agent structure pass to V1 Updated README.md to describe changes with this version --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4620e10..199d81ec 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,20 @@ the source code and customize it for your needs. The Agent requires Python 2.6 or 2.7. The instructions here assume that you're on a Mac. +## About this version + +This version of the PagerDuty Agent supports both V1 +(https://v2.developer.pagerduty.com/docs/events-api) and V2 +(https://v2.developer.pagerduty.com/docs/events-api-v2) Event APIs. If a version +is not explicitly stated with the -api argument, it will default to V1, and +should perform identically to the previous version. The V2 Api supports a richer +set of parameters that may be useful. + +In the background it determines the API endpoint by the checking if the +generated message contains "service_key" (V1) or "routing_key" +(V2) - in a simple textual manner (by the time it is on the queue it is a string +containing JSON and not a tree structure) + ## Developing @@ -36,8 +50,11 @@ Similarly, you can use the `pd-send` command immediately. ``` ~/w/pdagent/bin$ ./pd-send -h -usage: pd-send [-h] -k SERVICE_KEY -t {trigger,acknowledge,resolve} - [-d DESCRIPTION] [-i INCIDENT_KEY] [-f FIELDS] +usage: pd-send [-h] -k SERVICE_KEY [-api {V1,V2}] -t + {trigger,acknowledge,resolve} [-d DESCRIPTION] [-src SOURCE] + [-s {critical,warning,error,info}] [-cmp COMPONENT] [-g GROUP] + [-cls PROB_CLASS] [-i INCIDENT_KEY] [-c CLIENT] [-u CLIENT_URL] + [-f FIELDS] [-q] Queue up a trigger, acknowledge, or resolve event to PagerDuty. ... From 78a13ecb537a5d0405157f32f7412cd2f0fa367c Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Fri, 6 Apr 2018 10:44:45 +1000 Subject: [PATCH 8/9] Upated to only check mandatories for V2 items if V2 --- bin/pd-send | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index c396fb40..83290f2c 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -112,9 +112,9 @@ def main(): if args.event_type == "trigger": if (not args.description) or (not args.description.strip()): parser.error("Event type '%s' requires description" % args.event_type) - if (not args.severity) or ( not args.severity.strip()): + if ((not args.severity) or ( not args.severity.strip()) and args.api_version == "V2"): parser.error("Event type '%s' requires severity" % args.event_type) - if (not args.source) or ( not args.source.strip()): + if ((not args.source) or ( not args.source.strip()) and args.api_version == "V2"): parser.error("Event type '%s' requires origin" % args.event_type) else: if not args.incident_key: From deed9fd93fec34a7f0315371e589ff3c4b008f63 Mon Sep 17 00:00:00 2001 From: Jason Raynar Date: Fri, 6 Apr 2018 11:04:23 +1000 Subject: [PATCH 9/9] updated argument parsing to ignore checks for V2 items if using V1 api --- bin/pd-send | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/pd-send b/bin/pd-send index 83290f2c..ac998ca3 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -111,11 +111,11 @@ def main(): if args.event_type == "trigger": if (not args.description) or (not args.description.strip()): - parser.error("Event type '%s' requires description" % args.event_type) + parser.error("Event type '%s' requires description(-d)" % args.event_type) if ((not args.severity) or ( not args.severity.strip()) and args.api_version == "V2"): - parser.error("Event type '%s' requires severity" % args.event_type) + parser.error("Event type '%s' requires severity(-s)" % args.event_type) if ((not args.source) or ( not args.source.strip()) and args.api_version == "V2"): - parser.error("Event type '%s' requires origin" % args.event_type) + parser.error("Event type '%s' requires source(-src)" % args.event_type) else: if not args.incident_key: parser.error("Event type '%s' requires incident key" % args.event_type)