summaryrefslogtreecommitdiffstats
path: root/modules/migrate
diff options
context:
space:
mode:
Diffstat (limited to 'modules/migrate')
-rw-r--r--modules/migrate/application/clicommands/ConfigCommand.php119
-rw-r--r--modules/migrate/application/clicommands/NavigationCommand.php195
-rw-r--r--modules/migrate/application/clicommands/PreferencesCommand.php131
-rw-r--r--modules/migrate/library/Migrate/Config/UserDomainMigration.php378
-rw-r--r--modules/migrate/module.info5
5 files changed, 828 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;
+ }
+}
diff --git a/modules/migrate/library/Migrate/Config/UserDomainMigration.php b/modules/migrate/library/Migrate/Config/UserDomainMigration.php
new file mode 100644
index 0000000..855a0ab
--- /dev/null
+++ b/modules/migrate/library/Migrate/Config/UserDomainMigration.php
@@ -0,0 +1,378 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Migrate\Config;
+
+use Icinga\Application\Config;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\ResourceFactory;
+use Icinga\User;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+
+class UserDomainMigration
+{
+ protected $toDomain;
+
+ protected $fromDomain;
+
+ protected $map;
+
+ public static function fromMap(array $map)
+ {
+ $static = new static();
+
+ $static->map = $map;
+
+ return $static;
+ }
+
+ public static function fromDomains($toDomain, $fromDomain = null)
+ {
+ $static = new static();
+
+ $static->toDomain = $toDomain;
+ $static->fromDomain = $fromDomain;
+
+ return $static;
+ }
+
+ protected function mustMigrate(User $user)
+ {
+ if ($user->getUsername() === '*') {
+ return false;
+ }
+
+ if ($this->map !== null) {
+ return isset($this->map[$user->getUsername()]);
+ }
+
+ if ($this->fromDomain !== null && $user->hasDomain() && $user->getDomain() !== $this->fromDomain) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function migrateUser(User $user)
+ {
+ $migrated = clone $user;
+
+ if ($this->map !== null) {
+ $migrated->setUsername($this->map[$user->getUsername()]);
+ } else {
+ $migrated->setDomain($this->toDomain);
+ }
+
+ return $migrated;
+ }
+
+ protected function migrateAnnounces()
+ {
+ $announces = new AnnouncementIniRepository();
+
+ $query = $announces->select(array('author'));
+
+ if ($this->map !== null) {
+ $query->where('author', array_keys($this->map));
+ }
+
+ $migratedUsers = array();
+
+ foreach ($announces->select(array('author')) as $announce) {
+ $user = new User($announce->author);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ if (isset($migratedUsers[$user->getUsername()])) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $announces->update(
+ 'announcement',
+ array('author' => $migrated->getUsername()),
+ Filter::where('author', $user->getUsername())
+ );
+
+ $migratedUsers[$user->getUsername()] = true;
+ }
+ }
+
+ protected function migrateDashboards()
+ {
+ $directory = Config::resolvePath('dashboards');
+
+ $migration = array();
+
+ if (DirectoryIterator::isReadable($directory)) {
+ foreach (new DirectoryIterator($directory) as $username => $path) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$path] = dirname($path) . '/' . $migrated->getUsername();
+ }
+
+ foreach ($migration as $from => $to) {
+ rename($from, $to);
+ }
+ }
+ }
+
+ protected function migrateNavigation()
+ {
+ $directory = Config::resolvePath('navigation');
+
+ foreach (new DirectoryIterator($directory, 'ini') as $file) {
+ $config = Config::fromIni($file);
+
+ foreach ($config as $navigation) {
+ $owner = $navigation->owner;
+
+ if (! empty($owner)) {
+ $user = new User($owner);
+
+ if ($this->mustMigrate($user)) {
+ $migrated = $this->migrateUser($user);
+
+ $navigation->owner = $migrated->getUsername();
+ }
+ }
+
+ $users = $navigation->users;
+
+ if (! empty($users)) {
+ $users = StringHelper::trimSplit($users);
+
+ foreach ($users as &$username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $username = $migrated->getUsername();
+ }
+
+ $navigation->users = implode(',', $users);
+ }
+ }
+
+ $config->saveIni();
+ }
+ }
+
+ protected function migratePreferences()
+ {
+ $config = Config::app();
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->get('global', 'config_resource'));
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_user_preference', array('username'))
+ ->group('username');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_user_preference',
+ array('username' => $username),
+ Filter::where('username', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+
+ protected function migrateRoles()
+ {
+ $roles = Config::app('roles');
+
+ foreach ($roles as $role) {
+ $users = $role->users;
+
+ if (empty($users)) {
+ continue;
+ }
+
+ $users = StringHelper::trimSplit($users);
+
+ foreach ($users as &$username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $username = $migrated->getUsername();
+ }
+
+ $role->users = implode(',', $users);
+ }
+
+ $roles->saveIni();
+ }
+
+ protected function migrateUsers()
+ {
+ foreach (Config::app('authentication') as $name => $config) {
+ if (strtolower($config->backend) !== 'db') {
+ continue;
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_user', array('name'))
+ ->group('name');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('name', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_user',
+ array('name' => $username),
+ Filter::where('name', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+
+ foreach (Config::app('groups') as $name => $config) {
+ if (strtolower($config->backend) !== 'db') {
+ continue;
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_group_membership', array('username'))
+ ->group('username');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_group_membership',
+ array('username' => $username),
+ Filter::where('username', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+ }
+
+ public function migrate()
+ {
+ $this->migrateAnnounces();
+ $this->migrateDashboards();
+ $this->migrateNavigation();
+ $this->migratePreferences();
+ $this->migrateRoles();
+ $this->migrateUsers();
+ }
+}
diff --git a/modules/migrate/module.info b/modules/migrate/module.info
new file mode 100644
index 0000000..6eb2911
--- /dev/null
+++ b/modules/migrate/module.info
@@ -0,0 +1,5 @@
+Module: migrate
+Version: 2.11.4
+Description: Migrate module
+ This module was introduced with the domain-aware authentication feature in version 2.5.0.
+ It helps you migrating users and user configurations according to a given domain.