From 09a8785ceb856bf30e33e79a3300d05e7a3b2c57 Mon Sep 17 00:00:00 2001 From: hahn <axel.hahn@iml.unibe.ch> Date: Thu, 16 May 2019 11:15:18 +0200 Subject: [PATCH] ci server: update apmonitor and more checks --- .../classes/appmonitor-checks.class.php | 388 +++++++++++++++--- .../classes/appmonitor-client.class.php | 126 +++++- public_html/appmonitor/index.php | 214 ++++++---- .../deployment/classes/foremanapi.class.php | 107 ++++- 4 files changed, 688 insertions(+), 147 deletions(-) diff --git a/public_html/appmonitor/classes/appmonitor-checks.class.php b/public_html/appmonitor/classes/appmonitor-checks.class.php index 82df3271..a31e07f8 100644 --- a/public_html/appmonitor/classes/appmonitor-checks.class.php +++ b/public_html/appmonitor/classes/appmonitor-checks.class.php @@ -24,8 +24,14 @@ define("RESULT_ERROR", 3); * 2015-04-08 0.9 axel.hahn@iml.unibe.ch added sochket test: checkPortTcp<br> * 2018-06-29 0.24 axel.hahn@iml.unibe.ch add file and directory checks<br> * 2018-07-17 0.42 axel.hahn@iml.unibe.ch add port on mysqli check<br> + * 2018-07-26 0.46 axel.hahn@iml.unibe.ch fix mysql connection check with empty port param<br> + * 2018-08-14 0.47 axel.hahn@iml.unibe.ch appmonitor client: use timeout of 5 sec for tcp socket connections<br> + * 2018-08-15 0.49 axel.hahn@iml.unibe.ch cert check: added flag to skip verification<br> + * 2018-08-23 0.50 axel.hahn@iml.unibe.ch replace mysqli connect with mysqli real connect (to use a timeout)<br> + * 2018-08-27 0.52 axel.hahn@iml.unibe.ch add pdo connect (starting with mysql)<br> + * 2018-11-05 0.58 axel.hahn@iml.unibe.ch additional flag in http check to show content<br> * --------------------------------------------------------------------------------<br> - * @version 0.9 + * @version 0.77 * @author Axel Hahn * @link TODO * @license GPL @@ -36,22 +42,31 @@ class appmonitorcheck { // ---------------------------------------------------------------------- // CONFIG // ---------------------------------------------------------------------- - + /** * config container * @var array */ - private $_aConfig = array(); + protected $_aConfig = array(); /** * data of all checks * @var array */ - private $_aData = array(); - + protected $_aData = array(); - // protected $_units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); + + /** + * flat array with units for sizes + * @var array + */ protected $_units = array( 'B', 'KB', 'MB', 'GB', 'TB'); + + /** + * timeout in sec for tcp socket connections + * @var type + */ + protected $_iTimeoutTcp=5; // ---------------------------------------------------------------------- // CONSTRUCTOR @@ -72,13 +87,15 @@ class appmonitorcheck { * create basic array values for metadata * @return boolean */ - private function _createDefaultMetadata() { + protected function _createDefaultMetadata() { $this->_aData = array( "name" => $this->_aConfig["name"], "description" => $this->_aConfig["description"], "result" => RESULT_UNKNOWN, "value" => false, + "type" => false, + "time" => false, ); return true; } @@ -88,7 +105,7 @@ class appmonitorcheck { * @param type $iResult * @return type */ - private function _setResult($iResult) { + protected function _setResult($iResult) { return $this->_aData["result"] = (int) $iResult; } @@ -97,7 +114,7 @@ class appmonitorcheck { * @param type $iResult * @return type */ - private function _setOutput($s) { + protected function _setOutput($s) { return $this->_aData["value"] = (string) $s; } @@ -107,13 +124,13 @@ class appmonitorcheck { * @param type $s * @return boolean */ - private function _setReturn($iResult, $s) { + protected function _setReturn($iResult, $s) { $this->_setResult($iResult); $this->_setOutput($s); return true; } - private function _checkArrayKeys($aConfig, $sKeyList) { + protected function _checkArrayKeys($aConfig, $sKeyList) { foreach (explode(",", $sKeyList) as $sKey) { if (!array_key_exists($sKey, $aConfig)) { header('HTTP/1.0 503 Service Unavailable'); @@ -203,17 +220,22 @@ class appmonitorcheck { /** * helper function: read certificate data * called in checkCert() - * @param string $sUrl url to connect + * @param string $sUrl url to connect + * @param boolean $bVerifyCert flag: verify certificate; default: no check * @return array */ - protected function _certGetInfos($sUrl) { + protected function _certGetInfos($sUrl, $bVerifyCert) { $iTimeout=10; $aUrldata=parse_url($sUrl); $sHost = isset($aUrldata['host']) ? $aUrldata['host'] : false; $iPort = isset($aUrldata['port']) ? $aUrldata['port'] : ((isset($aUrldata['scheme']) && $aUrldata['scheme'] === 'https') ? 443 : false); - - $get = stream_context_create(array("ssl" => array("capture_peer_cert" => TRUE))); + $aSsl=array('capture_peer_cert' => true); + if($bVerifyCert){ + $aSsl['verify_peer']=false; + $aSsl['verify_peer_name']=false; + }; + $get = stream_context_create(array('ssl' => $aSsl)); if(!$get){ return array('_error' => 'Error: Cannot create stream_context'); } @@ -221,7 +243,7 @@ class appmonitorcheck { $errstr="stream_socket_client failed."; $read = stream_socket_client("ssl://$sHost:$iPort", $errno, $errstr, $iTimeout, STREAM_CLIENT_CONNECT, $get); if(!$read){ - return array('_error' => "Error $errno: $errstr; cannot create stream_context to ssl://$sHost:$iPort"); + return array('_error' => "Error $errno: $errstr; cannot create stream_socket_client with given stream_context to ssl://$sHost:$iPort; you can try to set the flag [verify] to false to check expiration date only."); } $cert = stream_context_get_params($read); if(!$cert){ @@ -238,26 +260,28 @@ class appmonitorcheck { * @param array $aParams * array( * "url" optional: url to connect check; default: own protocol + server + * "verify" optional: flag for verification of certificate or check for expiration only; default=true (=verification is on) * "warning" optional: count of days to warn; default=30 * ) * @return boolean */ - private function checkCert($aParams) { + protected function checkCert($aParams) { $sUrl = isset($aParams["url"]) ? $aParams["url"] : 'http'. ($_SERVER['HTTPS'] ? 's' : '') . '://' . $_SERVER['SERVER_NAME'] .':' . $_SERVER['SERVER_PORT'] ; - $iWarn = isset($aParams["warning"]) ? (int)($aParams["warning"]) : 30; + $bVerify = isset($aParams["verify"]) ? !!$aParams["verify"] : true; + $iWarn = isset($aParams["warning"]) ? (int)($aParams["warning"]) : 30; $sMessage="Checked url: $sUrl ... "; - $certinfo=$this->_certGetInfos($sUrl); + $certinfo=$this->_certGetInfos($sUrl, $bVerify); if(isset($certinfo['_error'])){ $this->_setReturn(RESULT_ERROR, $certinfo['_error'] . $sMessage); return true; } $sDNS=isset($certinfo['extensions']['subjectAltName']) ? $certinfo['extensions']['subjectAltName'] : false; - $sHost=parse_url($url,PHP_URL_HOST); + $sHost=parse_url($sUrl,PHP_URL_HOST); if(strstr($sDNS, 'DNS:'.$sHost)===false){ $this->_setReturn(RESULT_ERROR, 'Wrong certificate: '.$sHost.' is not listed as DNS alias in ['.$sDNS.'] ' . $sMessage); return true; @@ -278,7 +302,9 @@ class appmonitorcheck { return true; } // echo '<pre>'; - $this->_setReturn(RESULT_OK, 'OK, is valid. ' . $sMessage); + $this->_setReturn(RESULT_OK, 'OK. ' + .($bVerify ? 'Certificate is valid. ' : '(Verification is disabled; Check for expiration only.) ' ) + . $sMessage); return true; } @@ -302,7 +328,7 @@ class appmonitorcheck { } $power=0; foreach($this->_units as $sUnit){ - if (preg_match('/^[0-9\.]*'.$sUnit.'/', $sValue)){ + if (preg_match('/^[0-9\.\ ]*'.$sUnit.'/', $sValue)){ $i=preg_replace('/([0-9\.]*).*/', '$1', $sValue); $iReal=$i*pow(1024, $power); // die("FOUND: $sValue with unit ${sUnit} - 1024^$power * $i = $iReal"); @@ -325,7 +351,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkDiskfree($aParams) { + protected function checkDiskfree($aParams) { $this->_checkArrayKeys($aParams, "directory", "critical"); $sDirectory = $aParams["directory"]; @@ -378,7 +404,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkFile($aParams) { + protected function checkFile($aParams) { $aOK = array(); $aErrors = array(); $this->_checkArrayKeys($aParams, "filename"); @@ -413,42 +439,235 @@ class appmonitorcheck { return true; } + + /** + * compare function for 2 values + * @param any $verifyValue search value + * @param string $sCompare compare function; it is one of + * IS - + * GE - greater or equal + * GT - greater + * @param any $value existing value + * @return boolean + */ + protected function _compare($value, $sCompare, $verifyValue){ + switch ($sCompare){ + case "IS": + return $value===$verifyValue; + break; + case "GE": + return $value>=$verifyValue; + break; + case "GT": + return $value>$verifyValue; + break; + case "HAS": + return !!(strstr($value, $verifyValue)!==false); + break; + default: + die("FATAL ERROR: a compare function [$sCompare] is not implemented (yet)."); + break; + } + return false; + } + + /** * make http request and test response body * @param array $aParams * array( - * "url" url to fetch - * "contains" string that must exist in response body + * url string url to fetch + * headeronly boolean optional flag to fetch http response herader only; default: false = returns header and body + * follow boolean optional flag to follow a location; default: false = do not follow + * status integer test for an expected http status code; if none is given then test fails on status 400 and greater + * headercontains string test for a string in the http response header; it returns OK if the text was found + * headernotcontains string test for a string in the http response header; it returns OK if the text was not found + * headerregex string test for a regex in the http response header; it returns OK if the regex matches; example: "headerregex"=>"/lowercasematch/i" + * bodycontains string test for a string in the http response body; it returns OK if the text was found + * bodynotcontains string test for a string in the http response body; it returns OK if the text was not found + * bodyregex string test for a regex in the http response body; it returns OK if the regex matches; example: "headerregex"=>"/lowercasematch/i" * ) * @param integer $iTimeout value in sec; default: 5sec */ - private function checkHttpContent($aParams, $iTimeout = 5) { - $this->_checkArrayKeys($aParams, "url,contains"); + protected function checkHttpContent($aParams, $iTimeout = 5) { + $this->_checkArrayKeys($aParams, "url"); if (!function_exists("curl_init")) { header('HTTP/1.0 503 Service Unavailable'); die("ERROR: PHP CURL module is not installed."); } + $bShowContent = (isset($aParams["content"]) && $aParams["content"]) ? true : false; $ch = curl_init($aParams["url"]); + + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_NOBODY, isset($aParams["headeronly"]) && $aParams["headeronly"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, isset($aParams["follow"]) && $aParams["follow"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, $iTimeout); $res = curl_exec($ch); - curl_close($ch); if (!$res) { $this->_setReturn(RESULT_ERROR, 'ERROR: failed to fetch ' . $aParams["url"] . '.'); + return false; + } + $sOut=''; + $bError=false; + + $aInfos = curl_getinfo($ch); + /* + Array + ( + [url] => https://www.iml.unibe.ch/ + [content_type] => text/html; charset=utf-8 + [http_code] => 200 + [header_size] => 926 + [request_size] => 55 + [filetime] => -1 + [ssl_verify_result] => 20 + [redirect_count] => 0 + [total_time] => 1.812 + [namelookup_time] => 0 + [connect_time] => 0 + [pretransfer_time] => 0.015 + [size_upload] => 0 + [size_download] => 94654 + [speed_download] => 52237 + [speed_upload] => 0 + [download_content_length] => -1 + [upload_content_length] => -1 + [starttransfer_time] => 1.812 + [redirect_time] => 0 + [redirect_url] => + [primary_ip] => 130.92.30.80 + [certinfo] => Array + ( + ) + + [primary_port] => 443 + [local_ip] => 130.92.79.49 + [local_port] => 63597 + ) + */ + + curl_close($ch); + + $aTmp=explode("\r\n\r\n", $res, 2); + $sHttpHeader=$aTmp[0]; + $sHttpBody=isset($aTmp[1]) ? $aTmp[1] : false; + + // ---------- check functions + + // --- http status code + $sOut.="Http status: ".$aInfos['http_code']." - "; + if(isset($aParams["status"])){ + if($aInfos['http_code'] === $aParams["status"]){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } } else { - if (!strpos($res, $aParams["contains"]) === false) { - $this->_setReturn(RESULT_OK, 'OK: The text "' . $aParams["contains"] . '" was found in response of ' . $aParams["url"] . '.'); + if($aInfos['http_code'] >= 400){ + $sOut.="Error page detected<br>"; + $bError=true; + } else { + $sOut.="request successful<br>"; + } + } + // --- http header + if(isset($aParams["headercontains"]) && $aParams["headercontains"]){ + $sOut.="Http header contains "".$aParams["headercontains"]."" - "; + if(!strstr($sHttpHeader, $aParams["headercontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["headernotcontains"]) && $aParams["headernotcontains"]){ + $sOut.="Http header does not contain "".$aParams["headernotcontains"]."" - "; + if(strstr($sHttpHeader, $aParams["headernotcontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["headerregex"]) && $aParams["headerregex"]){ + $sOut.="Http header regex test "".$aParams["headerregex"]."" - "; + try{ + $bRegex=preg_match($aParams["headerregex"], $sHttpHeader); + if($bRegex){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + catch(Exception $e){ + $sOut.="Wrong REGEX<br>" . print_r($e, 1).'<br>'; + $bError=true; + } + } + // --- http body + if(isset($aParams["bodycontains"]) && $aParams["bodycontains"]){ + $sOut.="Http body contains "".$aParams["bodycontains"]."" - "; + if(!strstr($sHttpBody, $aParams["bodycontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["bodynotcontains"]) && $aParams["bodynotcontains"]){ + $sOut.="Http body does not contain "".$aParams["bodynotcontains"]."" - "; + if(strstr($sHttpBody, $aParams["bodynotcontains"])===false){ + $sOut.="compare OK<br>"; } else { - $this->_setReturn(RESULT_ERROR, 'ERROR: The text ' . $aParams["contains"] . ' was NOT found in response of ' . $aParams["url"] . '.'); + $sOut.="compare failed<br>"; + $bError=true; } } - return $res; + if(isset($aParams["bodyregex"]) && $aParams["bodyregex"]){ + $sOut.="Http body regex test "".$aParams["bodyregex"]."" - "; + try{ + $bRegex=preg_match($aParams["bodyregex"], $sHttpBody); + if($bRegex){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + catch(Exception $e){ + $sOut.="Wrong REGEX<br>" . print_r($e, 1).'<br>'; + $bError=true; + } + } + + if (!$bError) { + $this->_setReturn(RESULT_OK, + 'OK: http check "' . $aParams["url"] . '".<br>'.$sOut + ); + } else { + $this->_setReturn(RESULT_ERROR, + 'ERROR: http check "' . $aParams["url"] . '".<br>'.$sOut + ); + } + + /* + echo '<pre>'; + echo $sOut."<hr>"; + echo "<hr>HEADER: ".htmlentities($sHttpHeader)."<hr>"; + print_r($aParams); print_r($aInfos); + // echo htmlentities($sHttpBody); + die(); + */ + return true; } /** - * check mysql connection to a database using mysqli + * check mysql connection to a database using mysqli realconnect * @param array $aParams * array( * "server" @@ -458,18 +677,65 @@ class appmonitorcheck { * "port" <<< optional * ) */ - private function checkMysqlConnect($aParams) { + protected function checkMysqlConnect($aParams) { $this->_checkArrayKeys($aParams, "server,user,password,db"); + $mysqli=mysqli_init(); + if(!$mysqli){ + $this->_setReturn(RESULT_ERROR, 'ERROR: mysqli_init failed.'); + return false; + } + if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->_iTimeoutTcp)) { + $this->_setReturn(RESULT_ERROR, 'ERROR: setting mysqli_init failed.'); + return false; + } + $db = (isset($aParams["port"]) && $aParams["port"]) - ? mysqli_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"], $aParams["port"]) - : mysqli_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"]) + ? $mysqli->real_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"], $aParams["port"]) + : $mysqli->real_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"]) ; if ($db) { $this->_setReturn(RESULT_OK, "OK: Mysql database " . $aParams["db"] . " was connected"); - mysqli_close($db); + $mysqli->close(); return true; } else { - $this->_setReturn(RESULT_ERROR, "ERROR: Mysql database " . $aParams["db"] . " was not connected. " . mysqli_connect_error()); + $this->_setReturn(RESULT_ERROR, "ERROR: Mysql database " . $aParams["db"] . " was not connected. Error ".mysqli_connect_errno() .": ". mysqli_connect_error()); + return false; + } + } + /** + * check connection to a database using pdo + * see http://php.net/manual/en/pdo.drivers.php + * + * @param array $aParams + * array( + * "connect" + * "password" + * "user" + * ) + */ + protected function checkPdoConnect($aParams) { + $this->_checkArrayKeys($aParams, "connect,user,password"); + + try{ + $db = new PDO( + $aParams['connect'], + $aParams['user'], + $aParams['password'], + array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + + // timeout + // Not all drivers support this option; mysqli does + PDO::ATTR_TIMEOUT => $this->_iTimeoutTcp, + // mssql + // PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $this->_iTimeoutTcp, + ) + ); + $this->_setReturn(RESULT_OK, "OK: Database was connected with PDO " . $aParams['connect']); + $db=null; + return true; + } catch(PDOException $e) { + $this->_setReturn(RESULT_ERROR, "ERROR: Database was not connected " . $aParams['connect'] . " was not connected. Error ".$e->getMessage()); return false; } } @@ -483,7 +749,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkPortTcp($aParams) { + protected function checkPortTcp($aParams) { $this->_checkArrayKeys($aParams, "port"); $sHost = array_key_exists('host', $aParams) ? $aParams['host'] : '127.0.0.1'; @@ -496,6 +762,16 @@ class appmonitorcheck { $this->_setReturn(RESULT_UNKNOWN, "ERROR: $sHost:$iPort was not checked. socket_create() failed: " . socket_strerror(socket_last_error())); return false; } + // set socket timeout + socket_set_option( + $socket, + SOL_SOCKET, // socket level + SO_SNDTIMEO, // timeout option + array( + "sec"=>$this->_iTimeoutTcp, // timeout in seconds + "usec"=>0 + ) + ); $result = socket_connect($socket, $sHost, $iPort); if ($result === false) { @@ -510,16 +786,32 @@ class appmonitorcheck { } /** - * most simple check: set values + * most simple check: set given values + * Use this function to add a counter + * * @param array $aParams - * array( - * "result" integer; RESUL_nn - * "value" description text - * ) + * array keys: + * "result" integer; RESULT_nn + * "value" description text + * + * brainstorming for a future release + * + * "counter" optioal: array of counter values + * - "label" string: a label + * - "value" a number + * - "unit" string: unit, i.e. ms, MB/s + * - "type" one of counter | bars | bars-stacked | lines | ... ?? + * */ - private function checkSimple($aParams) { + protected function checkSimple($aParams) { $this->_checkArrayKeys($aParams, "result,value"); - return $this->_setReturn((int) $aParams["result"], $aParams["value"]); + $this->_setReturn((int) $aParams["result"], $aParams["value"]); + foreach(array('type', 'count', 'visual') as $sMyKey){ + if(isset($aParams[$sMyKey])){ + $this->_aData[$sMyKey]=$aParams[$sMyKey]; + } + } + return true; } /** @@ -530,7 +822,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkSqliteConnect($aParams) { + protected function checkSqliteConnect($aParams) { $this->_checkArrayKeys($aParams, "db"); if (!file_exists($aParams["db"])) { $this->_setReturn(RESULT_ERROR, "ERROR: Sqlite database file " . $aParams["db"] . " does not exist."); diff --git a/public_html/appmonitor/classes/appmonitor-client.class.php b/public_html/appmonitor/classes/appmonitor-client.class.php index 235b2217..297174ce 100644 --- a/public_html/appmonitor/classes/appmonitor-client.class.php +++ b/public_html/appmonitor/classes/appmonitor-client.class.php @@ -15,10 +15,14 @@ * --------------------------------------------------------------------------------<br> * <br> * --- HISTORY:<br> - * 2014-10-24 0.5 axel.hahn@iml.unibe.ch<br> - * 2014-11-21 0.6 axel.hahn@iml.unibe.ch removed meta::ts <br> + * 2014-10-24 0.5 axel.hahn@iml.unibe.ch<br> + * 2014-11-21 0.6 axel.hahn@iml.unibe.ch removed meta::ts <br> + * 2018-08-23 0.50 axel.hahn@iml.unibe.ch show version<br> + * 2018-08-24 0.51 axel.hahn@iml.unibe.ch method to show local status page<br> + * 2018-08-27 0.52 axel.hahn@iml.unibe.ch add pdo connect (starting with mysql)<br> + * 2018-11-05 0.58 axel.hahn@iml.unibe.ch additional flag in http check to show content<br> * --------------------------------------------------------------------------------<br> - * @version 0.6 + * @version 0.77 * @author Axel Hahn * @link TODO * @license GPL @@ -32,27 +36,35 @@ class appmonitor { * value is in seconds * @var int */ - private $_iDefaultTtl = 300; + protected $_sVersion = 'php-client-v0.77'; + + /** + * config: default ttl for server before requesting the client check again + * value is in seconds + * @var int + */ + protected $_iDefaultTtl = 300; /** * internal counter: greatest return value of all checks * @var type */ - private $_iMaxResult = false; + protected $_iMaxResult = false; /** * responded metadata of a website * @see _createDefaultMetadata() * @var array */ - private $_aMeta = array(); + protected $_aMeta = array(); /** * repended array of all checks * @see addCheck() * @var array */ - private $_aChecks = array(); + protected $_aChecks = array(); + protected $_iStart = false; /** @@ -63,21 +75,22 @@ class appmonitor { } // ---------------------------------------------------------------------- - // private function + // protected function // ---------------------------------------------------------------------- /** * create basic array values for metadata * @return boolean */ - private function _createDefaultMetadata() { + protected function _createDefaultMetadata() { $this->_iStart = microtime(true); $this->_aMeta = array( "host" => false, "website" => false, "ttl" => false, "result" => false, - "time" => false + "time" => false, + "version" => $this->_sVersion, ); // fill with default values @@ -92,7 +105,9 @@ class appmonitor { // ---------------------------------------------------------------------- /** - * set a host for metadata + * set the physical hostname for metadata; if no host is given then + * the php_uname("n") will be used to set one. + * * @param string $s hostname * @return bool */ @@ -104,7 +119,14 @@ class appmonitor { } /** - * set a vhost for metadata + * set a name for this website or application and its environment + * (dev, test, prod); + * + * If you have several application in subdirectories, i.e. /blog, /shop... + * then you should the path or any description to identify them too + * + * if no argument is given the name of HTTP_HOST will be used + * * @param string $sNewHost hostname * @return bool */ @@ -116,7 +138,9 @@ class appmonitor { } /** - * set a ttl value for + * set a ttl value in seconds to define how long a server should not + * ask again for a new status of this instance + * * @param int $iTTl TTL value in sec * @return boolean */ @@ -282,7 +306,7 @@ class appmonitor { /** * verify array values and abort with all found errors */ - private function _checkData() { + protected function _checkData() { $aErrors = array(); if (!count($this->_aChecks)) { @@ -384,5 +408,79 @@ class appmonitor { echo $sOut; return $sOut; } + /** + * output appmonitor client status as single html page + * + * @example <code> + * ob_start();<br> + * require __DIR__ . '/appmonitor/client/index.php'; + * $sJson=ob_get_contents(); + * ob_end_clean(); + * $oMonitor->renderHtmloutput($sJson); + * </code> + * + * @param string $sJson JSON of client output + */ + public function renderHtmloutput($sJson) { + + + header('Content-type: text/html'); + header('Cache-Control: cache'); + header('max-age: ' . $this->_aMeta["ttl"]); + $aMsg = array( + 0 => "OK", + 1 => "UNKNOWN", + 2 => "WARNING", + 3 => "ERROR" + ); + + // $sOut = print_r($sJson, 1); + $aData= json_decode($sJson, 1); + + // ----- Ausgabe human readable + $sOut=''; + $sOut.='<h2>Metadata</h2>' + . '<div class="meta'.(isset($aData['meta']['result']) ? ' result'.$aData['meta']['result'] : '' ) .'">' + . 'Status: ' . (isset($aData['meta']['result']) ? $aMsg[$aData['meta']['result']] : '?').'<br>' + . '</div>' + . 'Host: ' . (isset($aData['meta']['host']) ? '<span class="string">' . $aData['meta']['host'] .'</span>' : '?').'<br>' + . 'Website: ' . (isset($aData['meta']['website']) ? '<span class="string">' . $aData['meta']['website'].'</span>' : '?').'<br>' + // . 'Status: ' . (isset($aData['meta']['result']) ? '<span class="result'.$aData['meta']['result'].'">'. $aMsg[$aData['meta']['result']].'</span>' : '?').'<br>' + . 'Execution time: ' . (isset($aData['meta']['time']) ? '<span class="float">' . $aData['meta']['time'] .'</span>' : '?').'<br>' + + .'<h2>Checks</h2>' + ; + if (isset($aData['checks'][0]) && count($aData['checks'])){ + foreach($aData['checks'] as $aCheck){ + $sOut.= '<span class="result'.$aCheck['result'].'"> <strong>'.$aCheck['name'].'</strong></span> <br>' + . $aCheck['description'].'<br>' + // . '<span class="result'.$aCheck['result'].'">'.$aCheck['value'].'</span><br>' + . $aCheck['value'].'<br>' + . 'Execution time: ' . $aCheck['time'].'<br>' + . 'Status: ' . $aMsg[$aCheck['result']].'<br>' + . '<br>' + ; + } + } + $sOut.= '<hr>List of farbcodes: '; + foreach ($aMsg as $i=>$sText){ + $sOut.= '<span class="result'.$i.'">'. $sText.'</span> '; + } + $sOut = '<!DOCTYPE html><html><head>' + . '<style>' + . 'body{background:#fff; color:#444; font-family: verdana,arial; margin: 3em;}' + . '.result0{background:#aca; border-left: 1em solid #080; padding: 0 0.5em; }' + . '.result1{background:#ccc; border-left: 1em solid #aaa; padding: 0 0.5em; }' + . '.result2{background:#fc9; border-left: 1em solid #860; padding: 0 0.5em; }' + . '.result3{background:#f88; border-left: 1em solid #f00; padding: 0 0.5em; }' + . '</style>' + . '<title>' . __CLASS__ . '</title>' + . '</head><body>' + . '<h1>' . __CLASS__ . ' :: client status</h1>' + . $sOut + . '</body></html>'; + echo $sOut; + return $sOut; + } } diff --git a/public_html/appmonitor/index.php b/public_html/appmonitor/index.php index 52cf4eb7..05cdadd9 100644 --- a/public_html/appmonitor/index.php +++ b/public_html/appmonitor/index.php @@ -2,9 +2,6 @@ require_once('classes/appmonitor-client.class.php'); $oMonitor = new appmonitor(); -@include 'general_include.php'; - -$oMonitor->addTag('deployment'); $oMonitor->addCheck( array( @@ -20,90 +17,172 @@ $oMonitor->addCheck( ) ); +$sCfgfile='../../config/inc_projects_config.php'; + -require_once '../../config/inc_projects_config.php'; // ---------------------------------------------------------------------- -// needed directories +// config file // ---------------------------------------------------------------------- - $oMonitor->addCheck( array( - "name" => "tmp subdir", - "description" => "Check storage for temp directories, git checkouts for logmessages exists and is writable", + "name" => "read Cfg file", + "description" => "Check if config file is readable", "check" => array( "function" => "File", "params" => array( - "filename" => $aConfig['tmpDir'], - "dir" => true, - "writable" => true, + "filename" => $sCfgfile, + "file" => true, + "readable" => true, ), ), ) ); -$oMonitor->addCheck( - array( - "name" => "workdir", - "description" => "Check if base workdir exists and is writable", - "check" => array( - "function" => "File", - "params" => array( - "filename" => $aConfig['workDir'], - "dir" => true, - "writable" => true, - ), - ), - ) -); - -foreach(array('dataDir', 'buildDir', 'packageDir', 'archiveDir') as $sDirKey){ +require_once $sCfgfile; + +// echo '<pre>' . print_r($aConfig, 1) . '</pre>';die(); + +// ---------------------------------------------------------------------- +// directories +// ---------------------------------------------------------------------- + +foreach (array( + + 'tmpDir'=>array('dir'=>$aConfig['tmpDir'], 'descr'=>'Temp Dir mit git Daten'), + 'configDir'=>array('dir'=>$aConfig['configDir'], 'descr'=>'Ablage der Programm-Config'), + 'dataDir'=>array('dir'=>$aConfig['dataDir'], 'descr'=>'Basisverzeichnis fue DB, Projekt-Configs, SSH-Keys'), + 'dataDir/database'=>array('dir'=>$aConfig['dataDir'].'/database', 'descr'=>'DB-Ablage (Sqlite)'), + 'dataDir/projects'=>array('dir'=>$aConfig['dataDir'].'/projects', 'descr'=>'Projekt-Configdateien'), + 'dataDir/sshkeys'=>array('dir'=>$aConfig['dataDir'].'/sshkeys', 'descr'=>'SSH Keys'), + + 'buildDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Basisverzeichnis fuer Builds'), + 'packageDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Basisverzeichnis der Pakete und Versionen'), + 'archiveDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Ablage der gebuildeten Archive'), + +) as $sKey=>$aItem) { $oMonitor->addCheck( array( - "name" => "dir [$sDirKey]", - "description" => "Check if workdir $sDirKey exists and is writable", + "name" => "dir $sKey", + "description" => $aItem['descr'], "check" => array( "function" => "File", "params" => array( - "filename" => $aConfig[$sDirKey], + "filename" => $aItem['dir'], "dir" => true, "writable" => true, ), ), ) ); - } -foreach(array('dataDir', 'buildDir', 'packageDir', 'archiveDir') as $sDirKey){ - $oMonitor->addCheck( - array( - "name" => "Disk space in dir [$sDirKey]", - "description" => "Check if workdir $sDirKey has enough space", - "check" => array( - "function" => "Diskfree", - "params" => array( - "directory" => $aConfig[$sDirKey], - "warning" => "500MB", - "critical" => "100MB", - ), + +// ---------------------------------------------------------------------- +// foreman +// ---------------------------------------------------------------------- + +if(isset($aConfig['foreman']['api'])){ + + require_once(__DIR__.'/../deployment/classes/foremanapi.class.php'); + $oForeman = new ForemanApi($aConfig['foreman']); + + foreach (array('hostgroups', 'hosts') as $sForemanKey){ + $aFData=$oForeman->read(array( + 'request' => array( + array($sForemanKey), ), - ) - ); + 'response' => array( + 'id', 'title' + ), + )); + $oMonitor->addCheck( + array( + "name" => "Foreman $sForemanKey", + "description" => "Count of $sForemanKey", + "check" => array( + "function" => "Simple", + "params" => array( + "result" => count($aFData) ? RESULT_OK : RESULT_ERROR, + "value" => "Count of found $sForemanKey: " . count($aFData)."; response status: ".$oForeman->getResponseStatus() . "; Http-Code ".$oForeman->getResponseInfo('http_code'), + "count" => count($aFData), + "visual" => "simple", + ), + ), + ) + ); + } } - $oMonitor->addCheck( - array( - "name" => "Certificate check", - "description" => "Check if SSL cert is valid and does not expire soon", - "check" => array( - "function" => "Cert", - ), + +// ---------------------------------------------------------------------- +// ssh targets +// ---------------------------------------------------------------------- + +if(count($aConfig['mirrorPackages'])){ + foreach($aConfig['mirrorPackages'] as $sHostKey=>$aData){ + /* + [puppet.one] => Array + ( + [type] => rsync + [runas] => + [target] => copy-deployment@puppet.one.iml.unibe.ch:/var/shared/imldeployment ) - ); + */ + $sCmd='ssh '.$aData['target']. ' ls -1'; + exec($sCmd, $sOut, $iRc); + $oMonitor->addCheck( + array( + "name" => "mirror target $sHostKey", + "description" => "Sync target of generated packages", + "check" => array( + "function" => "Simple", + "params" => array( + "result" => $iRc ? RESULT_ERROR : RESULT_OK, + "value" => "$sCmd returns $sOut", + ), + ), + ) + ); + } +} +foreach(array( + array( + 'host'=>'gitlab.iml.unibe.ch', + 'port'=>22, + 'descr'=>'SSH port to Gitlab on gitlab.iml.unibe.ch' + ), + array( + 'host'=>'git-repo.iml.unibe.ch', + 'port'=>22, + 'descr'=>'SSH port to Gitlab on git-repo.iml.unibe.ch' + ), + array( + 'host'=>'github.com', + 'port'=>22, + 'descr'=>'SSH port to Github' + ), +) as $aDescr){ + $oMonitor->addCheck( + array( + "name" => 'port check '.$aDescr['host'].':'.$aDescr['port'], + "description" => $aDescr['descr'], + "check" => array( + "function" => "PortTcp", + "params" => array( + "port"=>$aDescr['port'], + "host"=>$aDescr['host'], + ), + ), + ) + ); +} + + // ---------------------------------------------------------------------- -// databases +// database // ---------------------------------------------------------------------- + $sSqlitefile=$aConfig['dataDir'].'/database/logs.db'; $oMonitor->addCheck( array( @@ -117,34 +196,7 @@ $oMonitor->addCheck( ), ) ); -$sSqlitefile2=$_SERVER['DOCUMENT_ROOT'].'/valuestore/data/versioncache.db'; -$oMonitor->addCheck( - array( - "name" => "Sqlite DB version cache", - "description" => "Connect sqlite db ". basename($sSqlitefile), - "check" => array( - "function" => "SqliteConnect", - "params" => array( - "db"=>$sSqlitefile2 - ), - ), - ) -); -// ---------------------------------------------------------------------- -// ssl cert -// ---------------------------------------------------------------------- -if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']){ - $oMonitor->addCheck( - array( - "name" => "Certificate check", - "description" => "Check if SSL cert is valid and does not expire soon", - "check" => array( - "function" => "Cert", - ), - ) - ); -} // Gesamt-Ergebnis - ohne Param=aut. max. Wert nehmen $oMonitor->setResult(); diff --git a/public_html/deployment/classes/foremanapi.class.php b/public_html/deployment/classes/foremanapi.class.php index 31fd9ddc..e0e9851e 100644 --- a/public_html/deployment/classes/foremanapi.class.php +++ b/public_html/deployment/classes/foremanapi.class.php @@ -114,8 +114,18 @@ class ForemanApi { ); + /** + * last request + * @var type + */ protected $_aRequest=array(); + /** + * last response + * @var type + */ + protected $_aResponse=array(); + // ---------------------------------------------------------------------- // constructor @@ -144,7 +154,9 @@ class ForemanApi { */ protected function log($sMessage, $sLevel = "info") { global $oCLog; - return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); + if($oCLog && method_exists($oLog, 'add')){ + return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); + } } /** @@ -228,6 +240,7 @@ class ForemanApi { if ($aRequest){ $this->_aRequest=$aRequest; } + $this->_aResponse=array(); $this->log(__FUNCTION__ . " start <pre>".print_r($this->_aRequest,1)."</pre>"); if (!function_exists("curl_init")) { die("ERROR: PHP CURL module is not installed."); @@ -262,11 +275,10 @@ class ForemanApi { $aReturn=array('info'=>curl_getinfo($ch), 'error'=>curl_error($ch)); curl_close($ch); $this->log(__FUNCTION__ . " status ".$aReturn['info']['http_code'].' '.$this->_aRequest['method']." $sFullUrl"); - $sHeader=substr($res, 0, $aReturn['info']['header_size']); $aReturn['header']=explode("\n", $sHeader); $aReturn['body']=str_replace($sHeader, "", $res); - + return $aReturn; } @@ -333,7 +345,7 @@ class ForemanApi { // check status $iStatuscode=$aReturn['info']['http_code']; if ($iStatuscode===0){ - $sStatus='wrong host'; + $sStatus='wrong host or no connect'; } if ($iStatuscode>=200 && $iStatuscode<400){ $sStatus='OK'; @@ -363,6 +375,7 @@ class ForemanApi { $aReturn['_OK']=$bOk; $aReturn['_status']=$sStatus; $this->_writeDebug(__FUNCTION__ . ' result of request <pre>'.print_r($aReturn,1).'</pre>'); + $this->_aResponse=$aReturn; return $aReturn; } @@ -512,5 +525,91 @@ class ForemanApi { return $this->makeRequest(); */ } + + // ---------------------------------------------------------------------- + // get response infos + // ---------------------------------------------------------------------- + + /** + * get curl info data from last response + * Array + ( + [url] => https://foreman/api/hostgroups/?&per_page=1000 + [content_type] => application/json; charset=utf-8 + [http_code] => 200 + [header_size] => 1034 + [request_size] => 218 + [filetime] => -1 + [ssl_verify_result] => 0 + [redirect_count] => 0 + [total_time] => 1.644417 + [namelookup_time] => 0.007198 + [connect_time] => 0.009012 + [pretransfer_time] => 0.0332 + [size_upload] => 0 + [size_download] => 119602 + [speed_download] => 72732 + [speed_upload] => 0 + [download_content_length] => -1 + [upload_content_length] => 0 + [starttransfer_time] => 1.642775 + [redirect_time] => 0 + [redirect_url] => + [primary_ip] => 10.0.2.10 + [certinfo] => Array + ( + ) + + [primary_port] => 443 + [local_ip] => 10.0.2.15 + [local_port] => 33906 + ) + * @param string $sKey get value of given key only + * @return any + */ + public function getResponseInfo($sKey=false){ + + if($sKey){ + return isset($this->_aResponse['info'][$sKey]) + ? $this->_aResponse['info'][$sKey] + : false + ; + } + return isset($this->_aResponse['info']) + ? $this->_aResponse['info'] + : false + ; + /* + return isset($sKey) && $sKey + ? isset($this->_aResponse['info'][$sKey]) + ? $this->_aResponse['info'][$sKey] + : false + : isset($this->_aResponse['info']) + ? $this->_aResponse['info'] + : false + ; + */ + } + /** + * get array of last http response header + * @return array + */ + public function getResponseHeader(){ + return isset($this->_aResponse['header']) + ? $this->_aResponse['header'] + : false + ; + } + /** + * get status of last request + * @return string + */ + public function getResponseStatus(){ + // print_r($this->_aResponse); die(); + return isset($this->_aResponse['_status']) + ? $this->_aResponse['_status'] + : false + ; + } } -- GitLab