Skip to content
Open
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
...
Expand Down
45 changes: 38 additions & 7 deletions bin/pd-send
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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)
Expand All @@ -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",
)
Expand Down
15 changes: 12 additions & 3 deletions pdagent/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
64 changes: 55 additions & 9 deletions pdagent/pdagentutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
):
Expand All @@ -120,4 +166,4 @@ def _build_event_json_str(
d,
separators=(',', ':'), # compact json str
sort_keys=True
)
)
14 changes: 12 additions & 2 deletions pdagent/sendevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down