icingadb-web/library/Icingadb/ProvidedHook/IcingaHealth.php
Alvar Penning 3e0bd96ec6 IcingaHealth: Fix version comparison
There are multiple possible outputs for an Icinga DB version. The
package version contain the git tag, with a leading "v". The development
version mimics git-describe(1), including a commit hash separated by a
dash after the semantic version.

The current version comparison uses PHP's builtin version_compare().

This results in leading "v"s to return invalid results. Furthermore, it
treats everything behind the version as an "any string"[^0], which is
smaller than dev, alpha, beta, and so on. Thus, any git-describe(1)
version of Icinga DB 1.4.0 would be considered smaller as 1.4.0.

Fixes #1230.

[^0]: https://www.php.net/manual/en/function.version-compare.php
2025-06-20 10:13:00 +02:00

158 lines
5.6 KiB
PHP

<?php
// Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2
namespace Icinga\Module\Icingadb\ProvidedHook;
use Icinga\Application\Hook\HealthHook;
use Icinga\Module\Icingadb\Common\Backend;
use Icinga\Module\Icingadb\Common\Database;
use Icinga\Module\Icingadb\Model\Instance;
use ipl\Web\Url;
class IcingaHealth extends HealthHook
{
use Database;
public const REQUIRED_ICINGADB_VERSION = '1.4.0';
/**
* normalizeVersion extracts a version string from Icinga DB's reported
* version comparable with REQUIRED_ICINGADB_VERSION via version_compare.
*
* @param string $version Icinga DB version string to normalize.
*
* @return string Normalized Icinga DB version string.
*/
public static function normalizeVersion(string $version): string
{
// Ignore leading "v" when the git tag is used, e.g., "v1.4.0".
$version = ltrim($version, 'v');
// Ignore everything after "-" used by git describe, e.g., "1.4.0-g...".
$version = explode('-', $version, 2)[0];
return $version;
}
/** @var Instance */
protected $instance;
public function getName(): string
{
return 'Icinga DB';
}
public function getUrl(): Url
{
return Url::fromPath('icingadb/health');
}
public function checkHealth()
{
$instance = $this->getInstance();
if ($instance === null) {
$this->setState(self::STATE_UNKNOWN);
$this->setMessage(t(
'Icinga DB is not running or not writing into the database'
. ' (make sure the icinga feature "icingadb" is enabled)'
));
} elseif ($instance->heartbeat->getTimestamp() < time() - 60) {
$this->setState(self::STATE_CRITICAL);
$this->setMessage(t(
'Icinga DB is not running or not writing into the database'
. ' (make sure the icinga feature "icingadb" is enabled)'
));
} elseif (
! isset($instance->icingadb_version)
|| version_compare(
self::normalizeVersion($instance->icingadb_version),
self::REQUIRED_ICINGADB_VERSION,
'<'
)
) {
$this->setState(self::STATE_CRITICAL);
$this->setMessage(sprintf(
t('Icinga DB is outdated, please upgrade to version %s or later.'),
self::REQUIRED_ICINGADB_VERSION
));
} else {
$this->setState(self::STATE_OK);
$warningMessages = [];
if (! $instance->icinga2_active_host_checks_enabled) {
$this->setState(self::STATE_WARNING);
$warningMessages[] = t('Active host checks are disabled');
}
if (! $instance->icinga2_active_service_checks_enabled) {
$this->setState(self::STATE_WARNING);
$warningMessages[] = t('Active service checks are disabled');
}
if (! $instance->icinga2_notifications_enabled) {
$this->setState(self::STATE_WARNING);
$warningMessages[] = t('Notifications are disabled');
}
if ($this->getState() === self::STATE_WARNING) {
$this->setMessage(implode("; ", $warningMessages));
} else {
$this->setMessage(sprintf(
t('Icinga DB is running and writing into the database. (Version: %s)'),
$instance->icingadb_version
));
}
}
if ($instance !== null) {
$this->setMetrics([
'heartbeat' => $instance->heartbeat->getTimestamp(),
'responsible' => $instance->responsible,
'icinga2_active_host_checks_enabled' => $instance->icinga2_active_host_checks_enabled,
'icinga2_active_service_checks_enabled' => $instance->icinga2_active_service_checks_enabled,
'icinga2_event_handlers_enabled' => $instance->icinga2_event_handlers_enabled,
'icinga2_flap_detection_enabled' => $instance->icinga2_flap_detection_enabled,
'icinga2_notifications_enabled' => $instance->icinga2_notifications_enabled,
'icinga2_performance_data_enabled' => $instance->icinga2_performance_data_enabled,
'icinga2_start_time' => $instance->icinga2_start_time->getTimestamp(),
'icinga2_version' => $instance->icinga2_version,
'icingadb_version' => $instance->icingadb_version ?? null,
'endpoint' => ['name' => $instance->endpoint->name]
]);
}
}
/**
* Get an Icinga DB instance
*
* @return ?Instance
*/
protected function getInstance()
{
if ($this->instance === null) {
$query = Instance::on($this->getDb())
->with('endpoint')
->columns([
'heartbeat',
'responsible',
'icinga2_active_host_checks_enabled',
'icinga2_active_service_checks_enabled',
'icinga2_event_handlers_enabled',
'icinga2_flap_detection_enabled',
'icinga2_notifications_enabled',
'icinga2_performance_data_enabled',
'icinga2_start_time',
'icinga2_version',
'endpoint.name'
]);
if (Backend::supportsDependencies()) {
$query->withColumns('icingadb_version');
}
$this->instance = $query->first();
}
return $this->instance;
}
}