diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:39:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:39:39 +0000 |
commit | 8ca6cc32b2c789a3149861159ad258f2cb9491e3 (patch) | |
tree | 2492de6f1528dd44eaa169a5c1555026d9cb75ec /modules/migrate/application/clicommands | |
parent | Initial commit. (diff) | |
download | icingaweb2-upstream.tar.xz icingaweb2-upstream.zip |
Adding upstream version 2.11.4.upstream/2.11.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
3 files changed, 445 insertions, 0 deletions
diff --git a/modules/migrate/application/clicommands/ConfigCommand.php b/modules/migrate/application/clicommands/ConfigCommand.php new file mode 100644 index 0000000..a5be144 --- /dev/null +++ b/modules/migrate/application/clicommands/ConfigCommand.php @@ -0,0 +1,119 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Migrate\Clicommands; + +use Icinga\Cli\Command; +use Icinga\Module\Migrate\Config\UserDomainMigration; +use Icinga\User; +use Icinga\Util\StringHelper; + +class ConfigCommand extends Command +{ + /** + * Rename users and user configurations according to a given domain + * + * The following configurations are taken into account: + * - Announcements + * - Preferences + * - Dashboards + * - Custom navigation items + * - Role configuration + * - Users and group memberships in database backends, if configured + * + * USAGE: + * + * icingacli migrate config users [options] + * + * OPTIONS: + * + * --to-domain=<to-domain> The new domain for the users + * + * --from-domain=<from-domain> Migrate only the users with the given domain. + * Use this switch in combination with --to-domain. + * + * --user=<user> Migrate only the given user in the format <user> or <user@domain> + * + * --map-file=<mapfile> File to use for renaming users + * + * --separator=<separator> Separator for the map file + * + * EXAMPLES: + * + * icingacli migrate config users ... + * + * Add the domain "icinga.com" to all users: + * + * --to-domain icinga.com + * + * Set the domain "example.com" on all users that have the domain "icinga.com": + * + * --to-domain example.com --from-domain icinga.com + * + * Set the domain "icinga.com" on the user "icingaadmin": + * + * --to-domain icinga.com --user icingaadmin + * + * Set the domain "icinga.com" on the users "icingaadmin@icinga.com" + * + * --to-domain example.com --user icingaadmin@icinga.com + * + * Rename users according to a map file: + * + * --map-file /path/to/mapfile --separator : + * + * MAPFILE: + * + * You may rename users according to a given map file. The map file must be separated by newlines. Each line then + * is specified in the format <from><separator><to>. The separator is specified with the --separator switch. + * + * Example content: + * + * icingaadmin:icingaadmin@icinga.com + * jdoe@example.com:jdoe@icinga.com + * rroe@icinga:rroe@icinga.com + */ + public function usersAction() + { + if ($this->params->has('map-file')) { + $mapFile = $this->params->get('map-file'); + $separator = $this->params->getRequired('separator'); + + $source = trim(file_get_contents($mapFile)); + $source = StringHelper::trimSplit($source, "\n"); + + $map = array(); + + array_walk($source, function ($item) use ($separator, &$map) { + list($from, $to) = StringHelper::trimSplit($item, $separator, 2); + $map[$from] = $to; + }); + + $migration = UserDomainMigration::fromMap($map); + } else { + $toDomain = $this->params->getRequired('to-domain'); + $fromDomain = $this->params->get('from-domain'); + $user = $this->params->get('user'); + + if ($user === null) { + $migration = UserDomainMigration::fromDomains($toDomain, $fromDomain); + } else { + if ($fromDomain !== null) { + $this->fail( + "Ambiguous arguments: Can't use --user in combination with --from-domain." + . " Please use the user@domain syntax for the --user switch instead." + ); + } + + $user = new User($user); + + $migrated = clone $user; + $migrated->setDomain($toDomain); + + $migration = UserDomainMigration::fromMap(array($user->getUsername() => $migrated->getUsername())); + } + } + + $migration->migrate(); + } +} diff --git a/modules/migrate/application/clicommands/NavigationCommand.php b/modules/migrate/application/clicommands/NavigationCommand.php new file mode 100644 index 0000000..06fb2a8 --- /dev/null +++ b/modules/migrate/application/clicommands/NavigationCommand.php @@ -0,0 +1,195 @@ +<?php + +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Migrate\Clicommands; + +use Icinga\Application\Config; +use Icinga\Application\Icinga; +use Icinga\Application\Logger; +use Icinga\Cli\Command; +use Icinga\Data\ConfigObject; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use Icinga\Module\Icingadb\Compat\UrlMigrator; +use Icinga\Util\DirectoryIterator; +use Icinga\Web\Request; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class NavigationCommand extends Command +{ + /** + * Migrate local user monitoring navigation items to the Icinga DB Web actions + * + * USAGE + * + * icingacli migrate navigation [options] + * + * OPTIONS: + * + * --user=<username> Migrate monitoring navigation items only for + * the given user. (Default *) + * + * --delete Remove the legacy files after successfully + * migrated the navigation items. + */ + public function indexAction() + { + $moduleManager = Icinga::app()->getModuleManager(); + if (! $moduleManager->hasEnabled('icingadb')) { + Logger::error('Icinga DB module is not enabled. Please verify that the module is installed and enabled.'); + return; + } + + $preferencesPath = Config::resolvePath('preferences'); + $sharedNavigation = Config::resolvePath('navigation'); + if (! file_exists($preferencesPath) && ! file_exists($sharedNavigation)) { + Logger::info('There are no local user navigation items to migrate'); + return; + } + + $rc = 0; + $user = $this->params->get('user'); + $directories = new DirectoryIterator($preferencesPath); + + foreach ($directories as $directory) { + $username = $user; + if ($username !== null && $directories->key() !== $username) { + continue; + } + + if ($username === null) { + $username = $directories->key(); + } + + $hostActions = $this->readFromIni($directory . '/host-actions.ini', $rc); + $serviceActions = $this->readFromIni($directory . '/service-actions.ini', $rc); + + Logger::info('Migrating monitoring navigation items for user "%s" to the Icinga DB Web actions', $username); + + if (! $hostActions->isEmpty()) { + $this->migrateNavigationItems($hostActions, $directory . '/icingadb-host-actions.ini', $rc); + } + + if (! $serviceActions->isEmpty()) { + $this->migrateNavigationItems($serviceActions, $directory . '/icingadb-service-actions.ini', $rc); + } + } + + // Start migrating shared navigation items + $hostActions = $this->readFromIni($sharedNavigation . '/host-actions.ini', $rc); + $serviceActions = $this->readFromIni($sharedNavigation . '/service-actions.ini', $rc); + + Logger::info('Migrating shared monitoring navigation items to the Icinga DB Web actions'); + + if (! $hostActions->isEmpty()) { + $this->migrateNavigationItems($hostActions, $sharedNavigation . '/icingadb-host-actions.ini', $rc); + } + + if (! $serviceActions->isEmpty()) { + $this->migrateNavigationItems($serviceActions, $sharedNavigation . '/icingadb-service-actions.ini', $rc); + } + + if ($rc > 0) { + Logger::error('Failed to migrate some monitoring navigation items'); + exit($rc); + } + + Logger::info('Successfully migrated all local user monitoring navigation items'); + } + + /** + * Migrate the given config to the given new config path + * + * @param Config $config + * @param string $path + * @param int $rc + */ + private function migrateNavigationItems($config, $path, &$rc) + { + $deleteLegacyFiles = $this->params->get('delete'); + $newConfig = $this->readFromIni($path, $rc); + $counter = 1; + + /** @var ConfigObject $configObject */ + foreach ($config->getConfigObject() as $configObject) { + // Change the config type from "host-action" to icingadb's new action + if (strpos($path, 'icingadb-host-actions') !== false) { + $configObject->type = 'icingadb-host-action'; + } else { + $configObject->type = 'icingadb-service-action'; + } + + $urlString = $configObject->get('url'); + if ($urlString !== null) { + $url = Url::fromPath($urlString, [], new Request()); + + try { + $urlString = UrlMigrator::transformUrl($url)->getAbsoluteUrl(); + $configObject->url = rawurldecode($urlString); + } catch (\InvalidArgumentException $err) { + // Do nothing + } + } + + $legacyFilter = $configObject->get('filter'); + if ($legacyFilter !== null) { + $filter = QueryString::parse($legacyFilter); + $filter = UrlMigrator::transformFilter($filter); + if ($filter !== false) { + $configObject->filter = rawurldecode(QueryString::render($filter)); + } else { + unset($configObject->filter); + } + } + + $section = $config->key(); + while ($newConfig->hasSection($section)) { + $section = $config->key() . $counter++; + } + + $newConfig->setSection($section, $configObject); + } + + try { + if (! $newConfig->isEmpty()) { + $newConfig->saveIni(); + + // Remove the legacy file only if explicitly requested + if ($deleteLegacyFiles) { + unlink($config->getConfigFile()); + } + } + } catch (NotWritableError $error) { + Logger::error('%s: %s', $error->getMessage(), $error->getPrevious()->getMessage()); + $rc = 256; + } + } + + /** + * Get the navigation items config from the given ini path + * + * @param string $path Absolute path of the ini file + * @param int $rc The return code used to exit the action + * + * @return Config + */ + private function readFromIni($path, &$rc) + { + try { + $config = Config::fromIni($path); + } catch (NotReadableError $error) { + if ($error->getPrevious() !== null) { + Logger::error('%s: %s', $error->getMessage(), $error->getPrevious()->getMessage()); + } else { + Logger::error($error->getMessage()); + } + + $config = new Config(); + $rc = 128; + } + + return $config; + } +} diff --git a/modules/migrate/application/clicommands/PreferencesCommand.php b/modules/migrate/application/clicommands/PreferencesCommand.php new file mode 100644 index 0000000..11d1edb --- /dev/null +++ b/modules/migrate/application/clicommands/PreferencesCommand.php @@ -0,0 +1,131 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Migrate\Clicommands; + +use Icinga\Application\Config; +use Icinga\Application\Logger; +use Icinga\Cli\Command; +use Icinga\Data\ConfigObject; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use Icinga\File\Ini\IniParser; +use Icinga\User; +use Icinga\User\Preferences\PreferencesStore; +use Icinga\Util\DirectoryIterator; + +class PreferencesCommand extends Command +{ + /** + * Migrate local INI user preferences to a database + * + * USAGE + * + * icingacli migrate preferences [options] + * + * OPTIONS: + * + * --resource=<resource-name> The resource to use, if no current database config backend is configured. + * --no-set-config-backend Do not set the given resource as config backend automatically + */ + public function indexAction() + { + $resource = Config::app()->get('global', 'config_resource'); + if (empty($resource)) { + $resource = $this->params->getRequired('resource'); + } + + $resourceConfig = ResourceFactory::getResourceConfig($resource); + if ($resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; + } + + $connection = ResourceFactory::createResource($resourceConfig); + + $preferencesPath = Config::resolvePath('preferences'); + if (! file_exists($preferencesPath)) { + Logger::info('There are no local user preferences to migrate'); + return; + } + + $rc = 0; + + $preferenceDirs = new DirectoryIterator($preferencesPath); + foreach ($preferenceDirs as $preferenceDir) { + if (! is_dir($preferenceDir)) { + continue; + } + + $userName = basename($preferenceDir); + + Logger::info('Migrating INI preferences for user "%s" to database...', $userName); + + $dbStore = new PreferencesStore(new ConfigObject(['connection' => $connection]), new User($userName)); + + try { + $dbStore->load(); + $dbStore->save( + new User\Preferences( + $this->loadIniFile($preferencesPath, (new User($userName))->getUsername()) + ) + ); + } catch (NotReadableError $e) { + if ($e->getPrevious() !== null) { + Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); + } else { + Logger::error($e->getMessage()); + } + + $rc = 128; + } catch (NotWritableError $e) { + Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); + $rc = 256; + } + } + + if ($rc > 0) { + Logger::error('Failed to migrate some user preferences'); + exit($rc); + } + + if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) { + $appConfig = Config::app(); + $globalConfig = $appConfig->getSection('global'); + $globalConfig['config_resource'] = $resource; + + try { + $appConfig->saveIni(); + } catch (NotWritableError $e) { + Logger::error('Failed to update general configuration: %s', $e->getMessage()); + exit(256); + } + } + + Logger::info('Successfully migrated all local user preferences to database'); + } + + private function loadIniFile(string $filePath, string $username): array + { + $preferences = []; + $preferencesFile = sprintf( + '%s/%s/config.ini', + $filePath, + strtolower($username) + ); + + if (file_exists($preferencesFile)) { + if (! is_readable($preferencesFile)) { + throw new NotReadableError( + 'Preferences INI file %s for user %s is not readable', + $preferencesFile, + $username + ); + } else { + $preferences = IniParser::parseIniFile($preferencesFile)->toArray(); + } + } + + return $preferences; + } +} |