343 lines
13 KiB
PHP
Executable File
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!');
|
|
} |