diff --git a/README.md b/README.md index 344e875..6a1991d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,31 @@ PHP PagerDuty Events API ========= -PHP implementation of the [PagerDuty Events API](https://v2.developer.pagerduty.com/v2/docs/events-api) +PHP implementation of the [PagerDuty Events API V2](https://v2.developer.pagerduty.com/docs/events-api-v2) -**Important**: v2 is a complete rewrite of the library. It is not backwards compatible with v1. -For all new projects i suggest using v2. It is more flexible and easier to use than v1, and overcomes some of its predecessor's design limitations. +UPGRADE NOTICE +--- +The [Events API V2](https://v2.developer.pagerduty.com/docs/events-api-v2) is **not backwards compatible** with the [Events API V1](https://v2.developer.pagerduty.com/docs/events-api). Hence, this API has changed. If you are upgrading from a [2.* release](https://github.com/adilbaig/pagerduty/releases), make sure you pay attention to the contructor of the `TriggerEvent` [![Latest Stable Version](https://poser.pugx.org/adilbaig/pagerduty/v/stable.svg)](https://packagist.org/packages/adilbaig/pagerduty) [![Total Downloads](https://poser.pugx.org/adilbaig/pagerduty/downloads.svg)](https://packagist.org/packages/adilbaig/pagerduty) -Features : ----- +Features +--- +- Compatible with [PD Events API V2](https://v2.developer.pagerduty.com/v2/docs/#the-events-api). - Trigger, acknowledge and resolve incidents. -- Support for [Event contexts](https://v2.developer.pagerduty.com/v2/docs/trigger-events#contexts). -- Works with [Events API V1](https://v2.developer.pagerduty.com/v2/docs/#the-events-api). +- Supports [Event contexts](https://v2.developer.pagerduty.com/v2/docs/trigger-events#contexts). Attach links and images to your incident reports. - Unit Tests -Installation : ----- +Installation +--- Add this line to your project's `composer.json` ```` { ... "require": { - "adilbaig/pagerduty": "2.*" + "adilbaig/pagerduty": "3.*" } ... } @@ -32,8 +33,8 @@ Add this line to your project's `composer.json` The packagist URL is https://packagist.org/packages/adilbaig/pagerduty -Usage: ----- +Usage +--- Trigger an event @@ -41,15 +42,18 @@ Trigger an event use \PagerDuty\TriggerEvent; use \PagerDuty\PagerDutyException; -$serviceKey = "1d334a4819fc4b67a795b1c54f9a"; //Replace this with the integration key of your service. +$routingKey = "1d334a4819fc4b67a795b1c54f9a"; //Replace this with the integration key of your service. -// In this example, we're triggering a "Service is down" message. +// In this example, we're triggering a "Service is down" message from a web server. try { - $responseCode = (new TriggerEvent($serviceKey, "Service is down"))->send(); + $event = new TriggerEvent($routingKey, "Service is down", "web-server-01", TriggerEvent::ERROR, true); + $responseCode = $event->send(); if($responseCode == 200) echo "Success"; - elseif($responseCode == 403) + elseif($responseCode == 429) echo "Rate Limited"; //You're being throttled. Slow down. + else // An error occured. Try again later + echo "Some error has occured. Try again later"; } catch(PagerDutyException $exception) { //This doesn't happen unless you've broken their guidelines. The API tries to minimize user mistakes var_dump($exception->getErrors()); } @@ -60,53 +64,73 @@ Automatically send only one PagerDuty incident for repeated errors ````php -//After this example, you will see just one incident on PD - -(new TriggerEvent($serviceKey, "Service is down", true))->send(); -(new TriggerEvent($serviceKey, "Service is down", true))->send(); -(new TriggerEvent($serviceKey, "Service is down", true))->send(); +//You will only see one incident on PD +(TriggerEvent($routingKey, "Service is down", "web-server-01", TriggerEvent::ERROR, true))->send(); +(TriggerEvent($routingKey, "Service is down", "web-server-01", TriggerEvent::ERROR, true))->send(); +(TriggerEvent($routingKey, "Service is down", "web-server-01", TriggerEvent::ERROR, true))->send(); ```` -Create a detailed 'trigger' event, add optional data. Dump the event and inspect -response from PD +Create a detailed 'trigger' event, add optional data. Dump the event and inspect response from PD ````php use \PagerDuty\TriggerEvent; use \PagerDuty\Context\LinkContext; use \PagerDuty\Context\ImageContext; -//Taken from the `trigger` example @ https://v2.developer.pagerduty.com/v2/docs/trigger-events +//Taken from the `trigger` example @ https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 +//Send a detailed event, and store the `dedup_key` generated on the server -$event = new TriggerEvent($serviceKey, "FAILURE for production/HTTP on machine srv01.acme.com"); +$event = new TriggerEvent( + $routingKey, + "Example alert on host1.example.com", + "monitoringtool:cloudvendor:central-region-dc-01:852559987:cluster/api-stats-prod-003", + TriggerEvent::INFO +); $event - ->setClient("Sample Monitoring Service") - ->setClientURL("https://monitoring.service.com") - ->setDetails(["ping time" => "1500ms", "load avg" => 0.75]) - ->addContext(new LinkContext("http://acme.pagerduty.com")) - ->addContext(new LinkContext("http://acme.pagerduty.com", "View the incident on PagerDuty")) - ->addContext(new ImageContext("https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1")); + ->setPayloadTimestamp("2015-07-17T08:42:58.315+0000") + ->setPayloadComponent("postgres") + ->setPayloadGroup("prod-datapipe") + ->setPayloadClass("deploy") + ->setPayloadCustomDetails(["ping_time" => "1500ms", "load_avg" => 0.75]) + ->addContext(new LinkContext("https://example.com/", "Link text")) + ->addContext(new ImageContext("https://www.pagerduty.com/wp-content/uploads/2016/05/pagerduty-logo-green.png", "https://example.com/", "Example text")) +; // Pass in the '$response' variable by reference if you want to inspect PD's response. This is optional, and you probably don't need this in production. $response = null; $responseCode = $event->send($response); -var_dump($response); +// In this case, we will save the `dedup_key` generated by the PD server +var_dump($response['dedup_key']); ```` Acknowledge an event +---- ````php -(new AcknowledgeEvent($serviceKey, "incident key"))->send(); +(new AcknowledgeEvent($routingKey, "dedup key"))->send(); ```` Resolve an event - +---- ````php -(new ResolveEvent($serviceKey, "incident key"))->send(); +(new ResolveEvent($routingKey, "dedup key"))->send(); +```` + +UnitTests +--- + +````bash +> ./vendor/bin/phpunit test/ +..... 5 / 5 (100%) + +Time: 37 ms, Memory: 4.00MB + +OK (5 tests, 6 assertions) ```` Questions ----- +--- **Q.** How do i get the service key from PagerDuty? @@ -115,7 +139,7 @@ Questions Read more here : https://v2.developer.pagerduty.com/v2/docs/events-api#getting-started Requirements ----- +--- This library needs the [curl pecl extension](https://php.net/curl). In Ubuntu 16.04, install it like so : diff --git a/src/PagerDuty/AcknowledgeEvent.php b/src/PagerDuty/AcknowledgeEvent.php index 282d013..fb1e794 100644 --- a/src/PagerDuty/AcknowledgeEvent.php +++ b/src/PagerDuty/AcknowledgeEvent.php @@ -10,10 +10,10 @@ class AcknowledgeEvent extends Event { - public function __construct($serviceKey, $incidentKey) + public function __construct($routingKey, $dedupKey) { - parent::__construct($serviceKey, 'acknowledge'); + parent::__construct($routingKey, 'acknowledge'); - $this->setIncidentKey($incidentKey); + $this->setDeDupKey($dedupKey); } } diff --git a/src/PagerDuty/Event.php b/src/PagerDuty/Event.php index 949d0cd..e0fe3f1 100644 --- a/src/PagerDuty/Event.php +++ b/src/PagerDuty/Event.php @@ -4,7 +4,7 @@ /** * An abstract Event - * + * * @author adil */ abstract class Event implements \ArrayAccess, \JsonSerializable @@ -14,40 +14,40 @@ abstract class Event implements \ArrayAccess, \JsonSerializable /** * ctor - * - * @param string $serviceKey - * @param string $type - One of trigger, acknowledge or resolve + * + * @param string $routingKey + * @param string $type - One of 'trigger', 'acknowledge' or 'resolve' */ - protected function __construct($serviceKey, $type) + protected function __construct($routingKey, $type) { - $this->dict['service_key'] = (string) $serviceKey; - $this->dict['event_type'] = (string) $type; + $this->dict['routing_key'] = (string) $routingKey; + $this->dict['event_action'] = (string) $type; } /** - * A unique incident key to identify an outage. - * Multiple events with the same $incidentKey will be grouped into one open incident. From the PD docs : - * - * `Submitting subsequent events for the same incident_key will result in those events being applied to an open incident - * matching that incident_key. Once the incident is resolved, any further events with the same incident_key will - * create a new incident (for trigger events) or be dropped (for acknowledge and resolve events).` - * - * @link https://v2.developer.pagerduty.com/docs/events-api#incident-de-duplication-and-incident_key - * - * @param string $incidentKey - * + * A unique key to identify an outage. + * Multiple events with the same $key will be grouped into one open incident. From the PD docs : + * + * `Submitting subsequent events for the same `dedup_key` will result in those events being applied to an open alert + * matching that `dedup_key`. Once the alert is resolved, any further events with the same `dedup_key` will create a + * new alert (for `trigger` events) or be dropped (for `acknowledge` and `resolve` events).` + * + * @link https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication + * + * @param string $key + * * @return self */ - public function setIncidentKey($incidentKey) + public function setDeDupKey($key) { - $this->dict['incident_key'] = (string) $incidentKey; + $this->dict['dedup_key'] = substr((string) $key, 0, 255); return $this; } /** - * Get the array + * Get the request json as an array * Useful for debugging or logging. - * + * * @return array */ public function toArray() @@ -57,13 +57,13 @@ public function toArray() /** * Send the event to PagerDuty - * + * * @param array $result (Opt)(Pass by reference) - If this parameter is given the result of the CURL call will be filled here. The response is an associative array. - * + * * @throws PagerDutyException - If status code == 400 - * + * * @return int - HTTP response code - * 200 - Event Processed + * 202 - Event Processed * 400 - Invalid Event. Throws a PagerDutyException * 403 - Rate Limited. Slow down and try again later. */ @@ -71,13 +71,13 @@ public function send(&$result = null) { $jsonStr = json_encode($this); - $curl = curl_init("https://events.pagerduty.com/generic/2010-04-15/create_event.json"); + $curl = curl_init("https://events.pagerduty.com/v2/enqueue"); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonStr); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', - 'Content-Length: ' . strlen($jsonStr) + 'Content-Length: ' . strlen($jsonStr), )); $result = json_decode(curl_exec($curl), true); diff --git a/src/PagerDuty/ResolveEvent.php b/src/PagerDuty/ResolveEvent.php index e5ab45e..853c4bd 100644 --- a/src/PagerDuty/ResolveEvent.php +++ b/src/PagerDuty/ResolveEvent.php @@ -10,10 +10,10 @@ class ResolveEvent extends Event { - public function __construct($serviceKey, $incidentKey) + public function __construct($routingKey, $dedupKey) { - parent::__construct($serviceKey, 'resolve'); + parent::__construct($routingKey, 'resolve'); - $this->setIncidentKey($incidentKey); + $this->setDeDupKey($dedupKey); } } diff --git a/src/PagerDuty/TriggerEvent.php b/src/PagerDuty/TriggerEvent.php index b0861af..dcd4bfc 100644 --- a/src/PagerDuty/TriggerEvent.php +++ b/src/PagerDuty/TriggerEvent.php @@ -8,86 +8,138 @@ /** * A 'trigger' event + * @link https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2 * * @author adil */ class TriggerEvent extends Event { + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const INFO = 'info'; + /** * * @var bool */ - private $autoIncidentKey; + private $autoDeDupKey; /** * Ctor * - * When $autoIncidentKey is true it auto-generates an `incident_key` based on $description. - * The incident_key is an md5 hash of the $description. This prevents - * PagerDuty from flooding admins with incidents that are essentially the same. + * @param string $routingKey - The routing key, taken from your PagerDuty 'Configuration' > 'Services' page + * @param string $summary - The Error message + * @param string $source - The unique location of the affected system, preferably a hostname or FQDN. + * @param string $severity - One of 'critical', 'error', 'warning' or 'info'. Use the constants above + * @param boolean $autoDeDupKey - If true, autogenerates a `dedup_key` based on the md5 hash of the $summary + */ + public function __construct($routingKey, $summary, $source, $severity, $autoDeDupKey = false) + { + parent::__construct($routingKey, 'trigger'); + $this->setPayloadSummary($summary); + $this->setPayloadSource($source); + $this->setPayloadSeverity($severity); + + $this->autoDeDupKey = (bool) $autoDeDupKey; + } + + /** + * A human-readable error message. + * This is what PD will read over the phone. + * + * @param string $summary * - * @param string $serviceKey - * @param string $description - * @param bool $autoIncidentKey (Opt) - Default: false + * @return self */ - public function __construct($serviceKey, $description, $autoIncidentKey = false) + public function setPayloadSummary($summary) { - parent::__construct($serviceKey, 'trigger'); - $this->setDescription($description); + $this->dict['payload']['summary'] = (string) $summary; + return $this; + } - $this->autoIncidentKey = (bool) $autoIncidentKey; + /** + * The unique location of the affected system, preferably a hostname or FQDN. + * + * @param string $source + * @return self + */ + public function setPayloadSource($source) + { + $this->dict['payload']['source'] = (string) $source; + return $this; } /** - * The name of the monitoring client that is triggering this event + * One of critical, error, warning or info. Use the class constants above * - * @param string $client + * @param string $value * @return self */ - public function setClient($client) + public function setPayloadSeverity($value) { - $this->dict['client'] = (string) $client; + $this->dict['payload']['severity'] = (string) $value; return $this; } /** - * The URL of the monitoring client that is triggering this event. + * The time this error occured. * - * @param string $clientUrl + * @param string $timestamp - Can be a datetime string as well. See the example @ https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 * @return self */ - public function setClientURL($clientUrl) + public function setPayloadTimestamp($timestamp) { - $this->dict['client_url'] = (string) $clientUrl; + $this->dict['payload']['timestamp'] = (string) $timestamp; return $this; } /** - * A human-readable error message. - * This is what PD will read over the phone. + * From the PD docs: "Component of the source machine that is responsible for the event, for example `mysql` or `eth0`" * - * @param string $desc + * @param string $value + * @return self + */ + public function setPayloadComponent($value) + { + $this->dict['payload']['component'] = (string) $value; + return $this; + } + + /** + * From the PD docs: "Logical grouping of components of a service, for example `app-stack`" * + * @param string $value * @return self */ - public function setDescription($desc) + public function setPayloadGroup($value) { - $this->dict['description'] = (string) $desc; + $this->dict['payload']['group'] = (string) $value; return $this; } /** - * An associative array of any user-defined values. - * This will be displayed along with the error in PD. Useful for debugging. + * From the PD docs: "The class/type of the event, for example `ping failure` or `cpu load`" * - * @param array $details - An associative array + * @param string $value + * @return self + */ + public function setPayloadClass($value) + { + $this->dict['payload']['class'] = (string) $value; + return $this; + } + + /** + * An associative array of additional details about the event and affected system * + * @param array $dict * @return self */ - public function setDetails(array $details) + public function setPayloadCustomDetails(array $dict) { - $this->dict['details'] = $details; + $this->dict['payload']['custom_details'] = $dict; return $this; } @@ -138,8 +190,8 @@ public function addContext(Context $context) public function toArray() { - if ($this->autoIncidentKey) { - $this->setIncidentKey("md5-" . md5($this->dict['description'])); + if ($this->autoDeDupKey) { + $this->setDeDupKey("md5-" . md5($this->dict['payload']['summary'])); } $ret = $this->dict; diff --git a/test/PagerDutyTest.php b/test/PagerDutyTest.php index 710cae3..3220a16 100644 --- a/test/PagerDutyTest.php +++ b/test/PagerDutyTest.php @@ -16,53 +16,62 @@ class PagerDutyTest extends \PHPUnit\Framework\TestCase public function testAckEvent() { - $serviceKey = "sv123"; - $incidentKey = "inc123"; + $routingKey = 'sv123'; + $dedupKey = 'inc123'; - $event = new AcknowledgeEvent($serviceKey, $incidentKey); + $event = new AcknowledgeEvent($routingKey, $dedupKey); - $expect = ['service_key' => $serviceKey, 'incident_key' => $incidentKey, 'event_type' => 'acknowledge']; + $expect = ['routing_key' => $routingKey, 'dedup_key' => $dedupKey, 'event_action' => 'acknowledge']; $this->assertEquals($expect, $event->toArray()); } public function testResolveEvent() { - $serviceKey = "sv123"; - $incidentKey = "inc123"; + $routingKey = 'sv123'; + $dedupKey = 'inc123'; - $event = new ResolveEvent($serviceKey, $incidentKey); + $event = new ResolveEvent($routingKey, $dedupKey); - $expect = ['service_key' => $serviceKey, 'incident_key' => $incidentKey, 'event_type' => 'resolve']; + $expect = ['routing_key' => $routingKey, 'dedup_key' => $dedupKey, 'event_action' => 'resolve']; $this->assertEquals($expect, $event->toArray()); } public function testTriggerEvent() { - $serviceKey = "sv123"; + $routingKey = 'sv123'; - $event = new TriggerEvent($serviceKey, "FAILURE for production/HTTP on machine srv01.acme.com"); + $event = new TriggerEvent($routingKey, 'FAILURE for production/HTTP on machine srv01.acme.com', 'localhost', TriggerEvent::ERROR); $event - ->setClient("Sample Monitoring Service") - ->setClientURL("https://monitoring.service.com") - ->setDetails(["ping time" => "1500ms", "load avg" => 0.75]) - ->addContext(new LinkContext("http://acme.pagerduty.com")) - ->addContext(new LinkContext("http://acme.pagerduty.com", "View the incident on PagerDuty")) - ->addContext(new ImageContext("https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1")) + ->setPayloadClass('ping failure') + ->setPayloadComponent('web server') + ->setPayloadTimestamp('2018-05-01T08:42:58.315+0000') + ->setPayloadCustomDetails(['ping_time' => '1500ms', 'load_avg' => 0.75]) + ->addContext(new LinkContext('http://acme.pagerduty.com')) + ->addContext(new LinkContext('http://acme.pagerduty.com', 'View the incident on PagerDuty')) + ->addContext(new ImageContext('https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1')) ; - $this->assertArrayNotHasKey('incident_key', $event->toArray()); + $this->assertArrayNotHasKey('dedup_key', $event->toArray()); $expect = [ - 'service_key' => $serviceKey, - 'event_type' => 'trigger', - 'description' => "FAILURE for production/HTTP on machine srv01.acme.com", - 'details' => ["ping time" => "1500ms", "load avg" => 0.75], - 'client' => "Sample Monitoring Service", - 'client_url' => "https://monitoring.service.com", + 'routing_key' => $routingKey, + 'event_action' => 'trigger', + 'payload' => [ + 'summary' => 'FAILURE for production/HTTP on machine srv01.acme.com', + 'source' => 'localhost', + 'severity' => 'error', + 'class' => 'ping failure', + 'component' => 'web server', + 'timestamp' => '2018-05-01T08:42:58.315+0000', + 'custom_details' => [ + 'ping_time' => '1500ms', + 'load_avg' => 0.75, + ], + ], 'contexts' => [ - ['type' => 'link', 'href' => "http://acme.pagerduty.com"], - ['type' => 'link', 'href' => "http://acme.pagerduty.com", 'text' => "View the incident on PagerDuty"], - ['type' => 'image', 'src' => "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1"], + ['type' => 'link', 'href' => 'http://acme.pagerduty.com'], + ['type' => 'link', 'href' => 'http://acme.pagerduty.com', 'text' => 'View the incident on PagerDuty'], + ['type' => 'image', 'src' => 'https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1'], ], ]; @@ -71,12 +80,12 @@ public function testTriggerEvent() public function testTriggerHashEvent() { - $serviceKey = "sv123"; + $routingKey = 'sv123'; - $msg = "FAILURE for production/HTTP on machine srv01.acme.com"; - $event = new TriggerEvent($serviceKey, $msg, true); + $msg = 'FAILURE for production/HTTP on machine srv01.acme.com'; + $event = new TriggerEvent($routingKey, $msg, 'localhost', TriggerEvent::ERROR, true); - $expect = ['incident_key' => "md5-" . md5($msg)]; + $expect = ['dedup_key' => 'md5-' . md5($msg)]; $this->assertArraySubset($expect, $event->toArray()); } @@ -85,7 +94,7 @@ public function testTriggerHashEvent() */ public function testTypeError() { - $event = new TriggerEvent("sv123", "Blah"); + $event = new TriggerEvent('sv123', 'Blah'); $event->setDetails(null); } }