scripts/cachet_notifyV2.php

343 lines
13 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
class Component
{
public $id = null;
public $name = null;
public $type = null;
public $status = null;
public $state = null;
public $state_type = null;
public function __construct($name, $type, $state, $state_type) {
$this->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!');
}