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. ... diff --git a/bin/pd-send b/bin/pd-send index 6a7856b6..ac998ca3 100755 --- a/bin/pd-send +++ b/bin/pd-send @@ -35,7 +35,12 @@ 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( + "-api", "--apiVersion", dest="api_version", + choices=["V1", "V2",], default="V1", + help="API version to target (default is V1 if not set)" ) parser.add_argument( "-t", "--event-type", dest="event_type", required=True, @@ -44,11 +49,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="(Payload)Source of issue" + ) + parser.add_argument( + "-s", "--severity", dest="severity", + choices=["critical", "warning", "error","info"], + help="(Payload)Severity value for the problem (-api V2 only)" + ) + parser.add_argument( + "-cmp", "--component", dest="component", + help="(Payload)Component value for the problem (-api V2 only)" + ) + parser.add_argument( + "-g", "--group", dest="group", + 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 (-api V2 only)" ) parser.add_argument( "-i", "--incident-key", dest="incident_key", - help="Incident Key" + help="Incident Key/Dedup key" ) parser.add_argument( "-c", "--client", dest="client", @@ -60,7 +86,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", @@ -85,7 +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(-s)" % args.event_type) + if ((not args.source) or ( not args.source.strip()) and args.api_version == "V2"): + 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) @@ -94,8 +124,9 @@ def main(): enqueuer = agent_config.get_enqueuer() incident_key, problems = queue_event( - enqueuer, - args.event_type, args.service_key, args.incident_key, args.description, + 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, agent_config.get_agent_id(), "pd-send", ) diff --git a/pdagent/constants.py b/pdagent/constants.py index cedf906a..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 = \ - "https://events.pagerduty.com/generic/2010-04-15/create_event.json" +# 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 4c7c0304..85a5c4b4 100644 --- a/pdagent/pdagentutil.py +++ b/pdagent/pdagentutil.py @@ -67,19 +67,24 @@ def utcnow_isoformat(time_calc=None): return time_calc.strftime("%Y-%m-%dT%H:%M:%SZ", time_calc.gmtime()) def queue_event( - enqueuer, - event_type, service_key, incident_key, description, client, client_url, details, - agent_id, queued_by, + 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, ): agent_context = { "agent_id": agent_id, "queued_by": queued_by, "queued_at": utcnow_isoformat() } - event = _build_event_json_str( - event_type, 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, agent_context + ) + 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,48 @@ 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 + ): + p = { + "custom_details": details + } + d = { + "payload": p, + "routing_key": service_key, + "event_action": event_type + } + if incident_key is not None: + d["dedup_key"] = incident_key + if description is not None: + 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: + 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 + ) + + +def _build_event_json_str_V1( event_type, service_key, incident_key, description, client, client_url, details, agent_context=None ): @@ -120,4 +166,4 @@ def _build_event_json_str( 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)