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

Event2 api agent #119

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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