diff --git a/README.md b/README.md index 34c9ad4..59d1f15 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,22 @@ reimport them bin/magento wearejh:stripped-db-provider:import-from-remote PROJECT NAME ``` -To skip admin accounts backup +#### To skip admin accounts backup ``` -bin/magento wearejh:stripped-db-provider:import-from-remote PROJECT NAME --no-admin-backup=1 +bin/magento wearejh:stripped-db-provider:import-from-remote PROJECT NAME --no-admin-backup=1 +``` + +#### Config backup + +The `config-backup` option accepts 3 values: +* *skip* - skips the backup of local config and imports everything from the remote +* *merge* - backs up only the paths configured under `stripped_db_provider/dump/config_paths_keep` (accepts MySQL wildcards `%`), + imports everything from the remote and merges the backed up config with the remote config +* *preserve-local* - backs up the whole `core_config_data` and restores it after the DB import from remote + +``` +bin/magento wearejh:stripped-db-provider:import-from-remote PROJECT NAME --config-backup=merge ``` ## Issues / Feature Request diff --git a/src/Console/ImportFromRemoteCommand.php b/src/Console/ImportFromRemoteCommand.php index f3e2c7b..a373143 100644 --- a/src/Console/ImportFromRemoteCommand.php +++ b/src/Console/ImportFromRemoteCommand.php @@ -18,6 +18,11 @@ class ImportFromRemoteCommand extends Command { private const ARGUMENT_PROJECT_NAME = 'source-project-name'; private const OPTION_NO_ADMIN_ACCOUNT_BACKUP = 'no-admin-backup'; + private const OPTION_CONFIG_DATA_BACKUP = 'config-backup'; + + private CONST VALUE_CONFIG_SKIP = 'skip'; + private CONST VALUE_CONFIG_PRESERVE = 'preserve-local'; + private CONST VALUE_CONFIG_MERGE = 'merge'; private const SUCCESS = 1; @@ -47,6 +52,13 @@ protected function configure() 'Set this flag to skip backup of local admin accounts', false ); + $this->addOption( + self::OPTION_CONFIG_DATA_BACKUP, + null, + InputOption::VALUE_OPTIONAL, + 'Set this flag to back up the whole core_config_data table', + self::VALUE_CONFIG_MERGE + ); } /** @@ -68,6 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $sourceProjectMeta = new ProjectMeta($input->getArgument(self::ARGUMENT_PROJECT_NAME)); $backupAdminAccounts = !$input->getOption(self::OPTION_NO_ADMIN_ACCOUNT_BACKUP); + $backupConfig = $this->getConfigBackupOption($input); $output->writeln('Downloading Database From Cloud Storage...'); $this->dbFacade->downloadDatabaseDump($sourceProjectMeta); @@ -84,6 +97,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->dbFacade->backupLocalAdminAccounts($sourceProjectMeta); } + if ($backupConfig === self::VALUE_CONFIG_PRESERVE) { + $output->writeln('Backing up config ...'); + $this->dbFacade->backupLocalConfig($sourceProjectMeta); + } else if ($backupConfig === self::VALUE_CONFIG_MERGE) { + $output->writeln('Backing up preconfigured config paths ...'); + $this->dbFacade->backupConfigValues($sourceProjectMeta); + } + $output->writeln("Starting the Database import"); $this->dbFacade->importDatabaseDump($sourceProjectMeta); $output->writeln("Database successfully imported."); @@ -92,17 +113,37 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Restoring local admin accounts ...'); $this->dbFacade->restoreLocalAdminAccountsFromBackup($sourceProjectMeta); } + + if ($backupConfig === self::VALUE_CONFIG_PRESERVE) { + $output->writeln('Restoring config ...'); + $this->dbFacade->restoreLocalConfigFromBackup($sourceProjectMeta); + } else if ($backupConfig === self::VALUE_CONFIG_MERGE) { + $output->writeln('Restoring preconfigured config paths ...'); + $this->dbFacade->restoreConfigValues(); + } } catch (Exception $e) { $output->writeln("{$e->getMessage()}"); } finally { if (isset($sourceProjectMeta)) { $this->dbFacade->cleanUpLocalDumpFiles($sourceProjectMeta); } - if ($backupAdminAccounts) { + if (isset($backupAdminAccounts) && $backupAdminAccounts) { $this->dbFacade->cleanUpAdminAccountsBackup($sourceProjectMeta); } + if (isset($backupConfig) && $backupConfig === self::VALUE_CONFIG_PRESERVE) { + $this->dbFacade->cleanUpConfigBackup($sourceProjectMeta); + } } return self::SUCCESS; } + + private function getConfigBackupOption(InputInterface $input): string + { + $configBackupOption = $input->getOption(self::OPTION_CONFIG_DATA_BACKUP); + if (!in_array($configBackupOption, [self::VALUE_CONFIG_SKIP, self::VALUE_CONFIG_PRESERVE, self::VALUE_CONFIG_MERGE])) { + $configBackupOption = self::VALUE_CONFIG_MERGE; + } + return $configBackupOption; + } } diff --git a/src/Model/Config.php b/src/Model/Config.php index 698e424..2bbfcd8 100644 --- a/src/Model/Config.php +++ b/src/Model/Config.php @@ -28,6 +28,7 @@ class Config * Dump Specific */ const XML_PATH_PROJECT_IGNORE_TABLES = 'stripped_db_provider/dump/project_ignore_tables'; + const XML_PATH_PROJECT_KEEP_CONFIG_PATHS = 'stripped_db_provider/dump/config_paths_keep'; public function __construct( private readonly ScopeConfigInterface $config, @@ -77,4 +78,14 @@ public function getLocalDbConfigData(string $key): ?string ) ); } + + public function getConfigPathsToKeep(): array + {; + $defaultPaths = explode( + ',', + (string) $this->config->getValue(self::XML_PATH_PROJECT_KEEP_CONFIG_PATHS) + ); + + return array_merge($defaultPaths, $projectIgnoredPaths); + } } diff --git a/src/Model/Db/DbAdminAccountsManager.php b/src/Model/Db/DbAdminAccountsManager.php index da5fb4c..c155665 100644 --- a/src/Model/Db/DbAdminAccountsManager.php +++ b/src/Model/Db/DbAdminAccountsManager.php @@ -56,7 +56,7 @@ public function restoreAdminAccountsBackup(ProjectMeta $projectMeta): void "mysql:host={$hostName};dbname={$dbName}", $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_USER), $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_PASSWORD), - ['skip-definer' => true] + ['skip-definer' => true, 'add-drop-table' => true, 'skip-triggers' => true] ); $dumper->restore($this->getAdminAccountsBackupFilePath($projectMeta)); } diff --git a/src/Model/Db/DbConfigBackupManager.php b/src/Model/Db/DbConfigBackupManager.php new file mode 100644 index 0000000..cd9f2c0 --- /dev/null +++ b/src/Model/Db/DbConfigBackupManager.php @@ -0,0 +1,101 @@ +config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_HOST); + $dbName = $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_NAME); + $dumper = new Mysqldump( + "mysql:host={$hostName};dbname={$dbName}", + $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_USER), + $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_PASSWORD), + [ + 'skip-definer' => true, + 'add-drop-table' => true, + 'include-tables' => [ + 'core_config_data' + ] + ] + ); + + $dumper->start($this->getConfigBackupFilePath($projectMeta)); + } + + public function restoreConfigBackup(ProjectMeta $projectMeta): void + { + $hostName = $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_HOST); + $dbName = $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_NAME); + $dumper = new Mysqldump( + "mysql:host={$hostName};dbname={$dbName}", + $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_USER), + $this->config->getLocalDbConfigData(ConfigOptionsListConstants::KEY_PASSWORD), + ['skip-definer' => true, 'add-drop-table' => true, 'skip-triggers' => true] + ); + $dumper->restore($this->getConfigBackupFilePath($projectMeta)); + } + + public function cleanUp(ProjectMeta $projectMeta): void + { + try { + $this->shell->execute("rm %s", [$this->getConfigBackupFilePath($projectMeta)]); + } catch (\Exception $e) { + //empty + } + } + + public function cacheConfigValuesToKeep(): void + { + $pathsToCache = $this->config->getConfigPathsToKeep(); + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName('core_config_data'); + + $select = $connection->select() + ->from($tableName,['*']); + + foreach ($pathsToCache as $pattern) { + $select->orWhere('path LIKE ?', $pattern); + } + + $this->configCache = $connection->fetchAll($select); + } + + public function restoreConfigValuesToKeep(): void + { + foreach ($this->configCache as $config) { + $this->resourceConnection->getConnection()->insertOnDuplicate( + $this->resourceConnection->getTableName('core_config_data'), + $config + ); + } + } + + private function getConfigBackupFilePath(ProjectMeta $projectMeta): string + { + return $projectMeta->getLocalDumpStoragePath() . self::BACKUP_FILENAME; + } +} diff --git a/src/Model/DbFacade.php b/src/Model/DbFacade.php index b4e3b9c..ca48e31 100644 --- a/src/Model/DbFacade.php +++ b/src/Model/DbFacade.php @@ -15,7 +15,8 @@ public function __construct( private Db\DbUploader $uploader, private Db\DbImporter $importer, private Db\DbCleaner $cleaner, - private Db\DbAdminAccountsManager $adminAccountsManager + private Db\DbAdminAccountsManager $adminAccountsManager, + private Db\DbConfigBackupManager $configBackupManager ) { } @@ -83,4 +84,29 @@ public function cleanUpAdminAccountsBackup(ProjectMeta $projectMeta): void { $this->adminAccountsManager->cleanUp($projectMeta); } + + public function backupLocalConfig(ProjectMeta $projectMeta): void + { + $this->configBackupManager->backupConfig($projectMeta); + } + + public function restoreLocalConfigFromBackup(ProjectMeta $projectMeta): void + { + $this->configBackupManager->restoreConfigBackup($projectMeta); + } + + public function cleanUpConfigBackup(ProjectMeta $projectMeta): void + { + $this->configBackupManager->cleanUp($projectMeta); + } + + public function backupConfigValues(): void + { + $this->configBackupManager->cacheConfigValuesToKeep(); + } + + public function restoreConfigValues(): void + { + $this->configBackupManager->restoreConfigValuesToKeep(); + } } diff --git a/src/etc/config.xml b/src/etc/config.xml index 958ddcb..d66d958 100644 --- a/src/etc/config.xml +++ b/src/etc/config.xml @@ -7,6 +7,9 @@ + + payment/% +