#!/usr/bin/env php id = $this->generateSerial(); $this->name = $name; $this->type = $type; $this->state = $state; $this->state_type = $state_type; } private function generateSerial() { $tokens = 'ABCDE0123456789'; $serial = ''; for ($j = 0; $j < 7; $j++) { $serial .= $tokens[rand(0, 14)]; } return $serial; } } class Cachet { public $body = null; public $code = null; private $api_key = null; private $api_url = null; private $cache = null; private $cache_path = '/tmp/.cachet_notifyV2.cache'; const CATCHET_STATUS_INVESTIGATING = 1; const CATCHET_STATUS_IDENTIFIED = 2; const CATCHET_STATUS_WATCHING = 3; const CATCHET_STATUS_FIXED = 4; const CATCHET_COMP_OPERATIONAL = 1; const CATCHET_COMP_PERF_ISSUES = 2; const CATCHET_COMP_PARTIAL_OUTAGE = 3; const CATCHET_COMP_MAJOR_OUTAGE = 4; const MSG_HOST_DOWN = 'Automated incident created by Icinga2. Host \'%s\' is currently experiencing an outage and the system administrator has been notified'; const MSG_SERVICE_DOWN = 'Automated incident created by Icinga2. Service \'%s\' is currently experiencing an outage and the system administrator has been notified'; const MSG_HOST_UP = 'Automated incident created by Icinga2. Host \'%s\' has recovered from it\'s outage'; const MSG_SERVICE_UP = 'Automated incident created by Icinga2. Service \'%s\' has recovered from it\'s outage'; public function __construct() { global $argv; // Get API information $search_paths = array(dirname($argv[0]), '/tmp', dirname(__FILE__)); foreach($search_paths as $path) { $api_key_path = $path . '/.cachet_api_key'; if (file_exists($api_key_path)) { $api_info = parse_ini_file($api_key_path); $this->api_key = $api_info['key']; $this->api_url = $api_info['url']; } } // Load cache if it exists if (file_exists($this->cache_path)) { $this->cache = json_decode(file_get_contents($this->cache_path)); } else { $this->cache = new stdClass(); } } public function cache_store($name, $value) { $this->cache->$name->expires = time() + 86400; // Expires in 24h? $this->cache->$name->data = $value; file_put_contents($this->cache_path, json_encode($this->cache, JSON_PRETTY_PRINT)); } public function cache_get($name) { if (!empty($this->cache->$name)) { if ($this->cache->$name->expires > time()) { return $this->cache->$name->data; } } return null; } public function newOutage($component) { $previous_outage = $this->findOutage($component->name); if (!empty($previous_outage)) { // Create an outage if there isn't a previous outage debug('Cachet::newOutage() Previous outage detected, aborting new outage'); return; } $component->status = 'outage'; // Set outage status $this->trackOutage($component); // Now we track outage // Create incident $msg = ($component->type == 'host') ? sprintf(self::MSG_HOST_DOWN, $component->name) : sprintf(self::MSG_SERVICE_DOWN, $component->name); $this->createIncident($component->name, "{$component->name} {$component->id}", $msg); } public function endOutage($component) { // Track outage $component->status = 'operational'; $this->trackOutage($component); // Create incident $msg = ($component->type == 'host') ? sprintf(self::MSG_HOST_UP, $component->name) : sprintf(self::MSG_SERVICE_UP, $component->name); $this->createIncident($component->name, "{$component->name} {$component->id}", $msg, self::CATCHET_STATUS_FIXED, self::CATCHET_COMP_OPERATIONAL); } public function trackOutage($component) { $tracker = new stdClass(); $tracker->{$component->id}->expires = time() + 3600; $tracker->{$component->id}->data = $component; $this->cache_store('tracker', $tracker); } public function findOutage($name) { $tracker = $this->cache_get('tracker'); if (!empty($tracker)) { foreach($tracker as $id => $component) { if ($component->expires > time()) { // Check if the outage is within the outage timeframe if ($name == $component->data->name && $component->data->status == 'outage') { return $component->data; // Return object } } else { // Let us do some cleanup unset($tracker->{$component->data->id}); $this->cache_store('tracker', $tracker); } } } return null; } public function createIncident($name, $title, $msg, $status = self::CATCHET_STATUS_IDENTIFIED, $component_status = self::CATCHET_COMP_MAJOR_OUTAGE) { global $incident_prefix, $cachet_notify_subscribers; $cachet_component = $this->getComponent($name); $data = array( 'name' => "$incident_prefix $title", 'message' => "Alert: $msg", 'status' => $status, 'component_id' => $cachet_component->id, 'component_status' => $component_status, 'notify' => $cachet_notify_subscribers, ); $this->curl('incidents', 'POST', $data); if ($this->code != 200) { $error_msg = $this->body->errors[0]->detail; throw new Exception('Unable to create ticket: ' . $error_msg, $this->code); } } public function updateIncident($component, $msg) { global $incident_prefix, $cachet_notify_subscribers; $incident_info = $this->getIncident("$incident_prefix $component"); $data = array( 'name' => "$incident_prefix $component", 'message' => $msg, 'status' => self::CATCHET_STATUS_FIXED, 'component_id' => $incident_info->component_id, 'component_status' => self::CATCHET_COMP_OPERATIONAL, 'notify' => $cachet_notify_subscribers, ); $this->curl("incidents/{$incident_info->id}", 'PUT', $data); if ($this->code != 200) { $error_msg = $this->body->errors[0]->detail; throw new Exception('Unable to update ticket: ' . $error_msg, $this->code); } } private function getComponent($name) { $components = $this->cache_get('components'); if (empty($components)) { $this->curl('components'); $components = $this->body->data; // Let's cache this request $this->cache_store('components', $components); } foreach ($components as $component) { if ($name == $component->name) { // We nailed it return $component; } } return false; } private function getIncident($name) { $incidents = $this->cache_get('incidents'); if (empty($incidents)) { $this->curl('incidents'); $incidents = $this->body->data; // Let's cache this request $this->cache_store('incidents', $incidents); } foreach ($incidents as $incident) { if ($name == $incident->name) { // We nailed it return $incident; } } return false; } private function curl($component, $action = 'GET', $data = null) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->api_url . $component); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $action); if ($data !== null) { $ch_data = http_build_query($data); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); } $ch_headers = array('X-Cachet-Token: ' . $this->api_key); curl_setopt($ch, CURLOPT_HTTPHEADER, $ch_headers); curl_setopt($ch, CURLOPT_HEADER, false); // Don't return headers curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return body $body = curl_exec($ch); // For PHP 5.4.x support if (empty($body)) { throw new Exception('Curl failed processing request: ' . curl_error($ch), curl_errno($ch)); } $this->body = json_decode($body); $this->code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); } } function debug($msg) { global $debug; if ($debug == true) { write_log(LOG_DEBUG, "DEBUG: $msg"); } } function logmsg($msg) { write_log(LOG_INFO, $msg); } function write_log($log_priority, $log_msg) { openlog('cachet_notifyV2', LOG_PID | LOG_PERROR, LOG_LOCAL1); syslog($log_priority, $log_msg); closelog(); } // This is for me to debug //include '.cachet_vars.php'; // Config $incident_prefix = '[Icinga2]'; $cachet_notify_subscribers = true; $debug = false; $cachet = new Cachet(); // Let us process eventype accordanly switch ($_ENV['EVENT_TYPE']) { case 'HOST': debug("EVENT_TYPE{HOST} detected"); if ($_ENV['HOST_STATE'] == 'DOWN' && $_ENV['HOST_STATE_TYPE'] == 'HARD') { $component = new Component($_ENV['COMPONENT_ID'], 'host', $_ENV['HOST_STATE'], $_ENV['HOST_STATE_TYPE']); logmsg("{$_ENV['COMPONENT_ID']}: KO HOST DOWN: Creating new outage"); $cachet->newOutage($component); } elseif ($_ENV['HOST_STATE'] == 'UP' && $_ENV['HOST_STATE_TYPE'] == 'HARD') { // We going to check if this component ever had outage $component = $cachet->findOutage($_ENV['COMPONENT_ID']); if (!empty($component)) { $component->state = $_ENV['HOST_STATE']; $component->state_type = $_ENV['HOST_STATE_TYPE']; logmsg("{$_ENV['COMPONENT_ID']}: OK HOST UP: Recovering from outage"); $cachet->endOutage($component); } else { debug("No incident was triggered for this EVENT_TYPE{HOST} for COMPONENT_ID{{$_ENV['COMPONENT_ID']}} with HOST_STATE{{$_ENV['HOST_STATE']}} and HOST_STATE_TYPE{{$_ENV['HOST_STATE_TYPE']}}."); debug('No previous outage was found for this component.'); } } else { debug("No incident was triggered for this EVENT_TYPE{HOST} for COMPONENT_ID{{$_ENV['COMPONENT_ID']}} with HOST_STATE{{$_ENV['HOST_STATE']}} and HOST_STATE_TYPE{{$_ENV['HOST_STATE_TYPE']}}"); } break; case 'SERVICE': debug("EVENT_TYPE{SERVICE} detected"); if ($_ENV['SERVICE_STATE'] == 'CRITICAL' && $_ENV['SERVICE_STATE_TYPE'] == 'HARD') { $component = new Component($_ENV['COMPONENT_ID'], 'service', $_ENV['SERVICE_STATE'], $_ENV['SERVICE_STATE_TYPE']); logmsg("{$_ENV['COMPONENT_ID']}: KO SERVICE CRITICAL: Creating new outage"); $cachet->newOutage($component); } elseif ($_ENV['SERVICE_STATE'] == 'OK' && $_ENV['SERVICE_STATE_TYPE'] == 'HARD') { // We going to check if this component ever had outage $component = $cachet->findOutage($_ENV['COMPONENT_ID']); if (!empty($component)) { $component->state = $_ENV['SERVICE_STATE']; $component->state_type = $_ENV['SERVICE_STATE_TYPE']; logmsg("{$_ENV['COMPONENT_ID']}: OK SERVICE OK: Recovering from outage"); $cachet->endOutage($component); } else { debug("No incident was triggered for this EVENT_TYPE{SERVICE} for COMPONENT_ID{{$_ENV['COMPONENT_ID']}} with SERVICE_STATE{{$_ENV['SERVICE_STATE']}} and SERVICE_STATE_TYPE{{$_ENV['SERVICE_STATE_TYPE']}}"); debug('No previous outage was found for this component.'); } } else { debug("No incident was triggered for this EVENT_TYPE{SERVICE} for COMPONENT_ID{{$_ENV['COMPONENT_ID']}} with SERVICE_STATE{{$_ENV['SERVICE_STATE']}} and SERVICE_STATE_TYPE{{$_ENV['SERVICE_STATE_TYPE']}}"); } break; default : throw new Exception('No Event Type detected!'); }