diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:43:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:43:12 +0000 |
commit | cd989f9c3aff968e19a3aeabc4eb9085787a6673 (patch) | |
tree | fbff2135e7013f196b891bbde54618eb050e4aaf /library/Director/KickstartHelper.php | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-director-cd989f9c3aff968e19a3aeabc4eb9085787a6673.tar.xz icingaweb2-module-director-cd989f9c3aff968e19a3aeabc4eb9085787a6673.zip |
Adding upstream version 1.10.2.upstream/1.10.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Director/KickstartHelper.php')
-rw-r--r-- | library/Director/KickstartHelper.php | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/library/Director/KickstartHelper.php b/library/Director/KickstartHelper.php new file mode 100644 index 0000000..5010255 --- /dev/null +++ b/library/Director/KickstartHelper.php @@ -0,0 +1,555 @@ +<?php + +namespace Icinga\Module\Director; + +use Exception; +use Icinga\Application\Config; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Director\Exception\NestingError; +use Icinga\Module\Director\Objects\IcingaApiUser; +use Icinga\Module\Director\Objects\IcingaCommand; +use Icinga\Module\Director\Objects\IcingaEndpoint; +use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Objects\IcingaZone; +use Icinga\Module\Director\Core\CoreApi; +use Icinga\Module\Director\Core\RestApiClient; +use RuntimeException; + +class KickstartHelper +{ + /** @var Db */ + protected $db; + + /** @var CoreApi */ + protected $api; + + /** @var IcingaApiUser */ + protected $apiUser; + + /** @var IcingaEndpoint */ + protected $deploymentEndpoint; + + /** @var IcingaEndpoint[] */ + protected $loadedEndpoints; + + /** @var IcingaEndpoint[] */ + protected $removeEndpoints; + + /** @var IcingaZone[] */ + protected $loadedZones; + + /** @var IcingaZone[] */ + protected $removeZones; + + /** @var IcingaCommand[] */ + protected $loadedCommands; + + /** @var IcingaCommand[] */ + protected $removeCommands; + + protected $config = [ + 'endpoint' => null, + 'host' => null, + 'port' => null, + 'username' => null, + 'password' => null, + ]; + + /** + * KickstartHelper constructor. + * @param Db $db + */ + public function __construct(Db $db) + { + $this->db = $db; + } + + /** + * Trigger a complete kickstart run + */ + public function run() + { + $this->fetchEndpoints() + ->reconnectToDeploymentEndpoint() + ->fetchZones() + ->fetchCommands() + ->storeZones() + ->storeEndpoints() + ->storeCommands() + ->removeEndpoints() + ->removeZones() + ->removeCommands(); + + $this->apiUser()->store(); + } + + /** + * @return bool + */ + public function isConfigured() + { + $config = $this->fetchConfigFileSection(); + return array_key_exists('endpoint', $config) + && array_key_exists('username', $config); + } + + /** + * @return KickstartHelper + * @throws ProgrammingError + */ + public function loadConfigFromFile() + { + return $this->setConfig($this->fetchConfigFileSection()); + } + + /** + * @return array + */ + protected function fetchConfigFileSection() + { + return Config::module('director', 'kickstart') + ->getSection('config') + ->toArray(); + } + + /** + * @param array $config + * @return $this + * @throws ProgrammingError + */ + public function setConfig($config) + { + foreach ($config as $key => $value) { + if ($value === '') { + continue; + } + + if (! array_key_exists($key, $this->config)) { + throw new ProgrammingError( + '"%s" is not a valid config setting for the kickstart helper', + $key + ); + } + + $this->config[$key] = $value; + } + + return $this; + } + + /** + * @return bool + */ + public function isRequired() + { + $stats = $this->db->getObjectSummary(); + return (int) $stats['apiuser']->cnt_total === 0; + } + + /** + * @param $key + * @param mixed $default + * @return mixed + */ + protected function getValue($key, $default = null) + { + if ($this->config[$key] === null) { + return $default; + } + + return $this->config[$key]; + } + + /** + * @return IcingaApiUser + * @throws \Icinga\Exception\NotFoundError + */ + protected function apiUser() + { + if ($this->apiUser === null) { + $name = $this->getValue('username'); + + $user = IcingaApiUser::create(array( + 'object_name' => $this->getValue('username'), + 'object_type' => 'external_object', + 'password' => $this->getValue('password') + ), $this->db); + + if (IcingaApiUser::exists($name, $this->db)) { + $this->apiUser = IcingaApiUser::load($name, $this->db)->replaceWith($user); + } else { + $this->apiUser = $user; + } + + $this->apiUser->store(); + } + + return $this->apiUser; + } + + /** + * @param IcingaObject[] $objects + * @return IcingaObject[] + * @throws NestingError + */ + protected function sortByParent(array $objects) + { + $sorted = array(); + + $cnt = 0; + while (! empty($objects)) { + $cnt++; + if ($cnt > 20) { + $this->throwObjectLoop($objects); + } + + $unset = array(); + foreach ($objects as $key => $object) { + $parentName = $object->get('parent'); + if ($parentName === null || array_key_exists($parentName, $sorted)) { + $sorted[$object->getObjectName()] = $object; + $unset[] = $key; + } + } + + foreach ($unset as $key) { + unset($objects[$key]); + } + } + + return $sorted; + } + + /** + * @param IcingaObject[] $objects + * @throws NestingError + */ + protected function throwObjectLoop(array $objects) + { + $names = array(); + if (empty($objects)) { + $class = 'Nothing'; + } else { + $class = explode('/\\/', get_class(current($objects)))[0]; + } + + foreach ($objects as $object) { + $names[] = $object->getObjectName(); + } + + throw new NestingError( + 'Loop detected while resolving %s: %s', + $class, + implode(', ', $names) + ); + } + + /** + * @return $this + * @throws NestingError + * @throws \Icinga\Exception\NotFoundError + */ + protected function fetchZones() + { + $db = $this->db; + $this->loadedZones = $this->sortByParent( + $this->api()->setDb($db)->getZoneObjects() + ); + + return $this; + } + + /** + * @return $this + * @throws \Icinga\Module\Director\Exception\DuplicateKeyException + * @throws \Icinga\Exception\NotFoundError + */ + protected function storeZones() + { + $db = $this->db; + $existing = IcingaObject::loadAllExternalObjectsByType('zone', $db); + + foreach ($this->loadedZones as $name => $object) { + if (array_key_exists($name, $existing)) { + $object = $existing[$name]->replaceWith($object); + unset($existing[$name]); + } + + $object->store(); + } + + $this->removeZones = $existing; + + return $this; + } + + /** + * @return $this + */ + protected function removeZones() + { + return $this->removeObjects($this->removeEndpoints, 'External Zone'); + } + + /** + * @return $this + * @throws \Icinga\Module\Director\Exception\DuplicateKeyException + * @throws \Icinga\Exception\NotFoundError + */ + protected function fetchEndpoints() + { + $db = $this->db; + $this->loadedEndpoints = $this->api()->setDb($db)->getEndpointObjects(); + + $master = $this->getValue('endpoint'); + if (array_key_exists($master, $this->loadedEndpoints)) { + $apiuser = $this->apiUser(); + $apiuser->store(); + $object = $this->loadedEndpoints[$master]; + $object->apiuser = $apiuser->object_name; + $this->deploymentEndpoint = $object; + } + + return $this; + } + + /** + * @return $this + * @throws \Icinga\Exception\NotFoundError + * @throws \Icinga\Module\Director\Exception\DuplicateKeyException + */ + protected function storeEndpoints() + { + $db = $this->db; + $existing = IcingaObject::loadAllExternalObjectsByType('endpoint', $db); + + foreach ($this->loadedEndpoints as $name => $object) { + if (array_key_exists($name, $existing)) { + $object = $existing[$name]->replaceWith($object); + unset($existing[$name]); + } + + $object->store(); + } + + $this->removeEndpoints = $existing; + + $db->settings()->master_zone = $this->deploymentEndpoint->zone; + + return $this; + } + + /** + * @return $this + */ + protected function removeEndpoints() + { + return $this->removeObjects($this->removeEndpoints, 'External Endpoint'); + } + + /** + * @return $this + * @throws ConfigurationError + */ + protected function reconnectToDeploymentEndpoint() + { + $master = $this->getValue('endpoint'); + + if (! $this->deploymentEndpoint) { + throw new ConfigurationError( + 'I found no Endpoint object called "%s" on %s:%d', + $master, + $this->getHost(), + $this->getPort() + ); + } + + $ep = $this->deploymentEndpoint; + + $epHost = $ep->get('host'); + if (! $epHost) { + $epHost = $ep->getObjectName(); + } + + try { + $this->switchToDeploymentApi()->getStatus(); + } catch (Exception $e) { + throw new ConfigurationError( + 'I was unable to re-establish a connection to the Endpoint "%s" (%s:%d).' + . ' When reconnecting to the configured Endpoint (%s:%d) I get an error: %s' + . ' Please re-check your Icinga 2 endpoint configuration', + $master, + $this->getHost(), + $this->getPort(), + $epHost, + $ep->get('port'), + $e->getMessage() + ); + } + + return $this; + } + + /** + * @return $this + * @throws \Icinga\Exception\NotFoundError + */ + protected function fetchCommands() + { + $api = $this->api()->setDb($this->db); + $this->loadedCommands = array_merge( + $api->getSpecificCommandObjects('Check'), + $api->getSpecificCommandObjects('Notification'), + $api->getSpecificCommandObjects('Event') + ); + + return $this; + } + + /** + * @return $this + * @throws \Icinga\Exception\NotFoundError + * @throws \Icinga\Module\Director\Exception\DuplicateKeyException + */ + protected function storeCommands() + { + $db = $this->db; + $existing = IcingaObject::loadAllExternalObjectsByType('command', $db); + + foreach ($this->loadedCommands as $name => $object) { + if (array_key_exists($name, $existing)) { + $object = $existing[$name]->replaceWith($object); + unset($existing[$name]); + } + + $object->store(); + } + + $this->removeCommands = $existing; + + return $this; + } + + /** + * @return $this + */ + protected function removeCommands() + { + return $this->removeObjects($this->removeCommands, 'External Command'); + } + + protected function removeObjects(array $objects, $typeName) + { + foreach ($objects as $object) { + try { + $object->delete(); + } catch (Exception $e) { + throw new RuntimeException(sprintf( + "Failed to remove %s '%s', it's eventually still in use", + $typeName, + $object->getObjectName() + ), 0, $e); + } + } + + return $this; + } + + /** + * @param Db $db + * @return $this + */ + public function setDb(Db $db) + { + $this->db = $db; + return $this; + } + + /** + * @return string + */ + protected function getHost() + { + return $this->getValue('host', $this->getValue('endpoint')); + } + + /** + * @return int + */ + protected function getPort() + { + return (int) $this->getValue('port', 5665); + } + + /** + * @return CoreApi + * @throws \Icinga\Exception\NotFoundError + */ + protected function getDeploymentApi() + { + unset($this->api); + $ep = $this->deploymentEndpoint; + + $epHost = $ep->get('host'); + if (!$epHost) { + $epHost = $ep->object_name; + } + + $client = new RestApiClient( + $epHost, + $ep->get('port') + ); + + $apiuser = $this->apiUser(); + $client->setCredentials($apiuser->object_name, $apiuser->password); + + $api = new CoreApi($client); + $api->setDb($this->db); + + return $api; + } + + /** + * @return CoreApi + * @throws \Icinga\Exception\NotFoundError + */ + protected function getConfiguredApi() + { + unset($this->api); + $client = new RestApiClient( + $this->getHost(), + $this->getPort() + ); + + $apiuser = $this->apiUser(); + $client->setCredentials($apiuser->object_name, $apiuser->password); + + $api = new CoreApi($client); + $api->setDb($this->db); + + return $api; + } + + /** + * @return CoreApi + * @throws \Icinga\Exception\NotFoundError + */ + protected function switchToDeploymentApi() + { + return $this->api = $this->getDeploymentApi(); + } + + /** + * @return CoreApi + * @throws \Icinga\Exception\NotFoundError + */ + protected function api() + { + if ($this->api === null) { + $this->api = $this->getConfiguredApi(); + } + + return $this->api; + } +} |