From d03bd706e2ba6c6f180b7eb502bee0e5a6f6c627 Mon Sep 17 00:00:00 2001 From: "denis.flaven@combodo.com" Date: Wed, 29 Mar 2023 16:53:38 +0200 Subject: [PATCH] Support of extra files (configurable) in the backup. --- .../2.x/itop-backup/dbrestore.class.inc.php | 29 +++++ setup/backup.class.inc.php | 20 ++++ test/setup/DBBackupDataTest.php | 105 ++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 test/setup/DBBackupDataTest.php diff --git a/datamodels/2.x/itop-backup/dbrestore.class.inc.php b/datamodels/2.x/itop-backup/dbrestore.class.inc.php index 55b811daf..259c72990 100644 --- a/datamodels/2.x/itop-backup/dbrestore.class.inc.php +++ b/datamodels/2.x/itop-backup/dbrestore.class.inc.php @@ -187,6 +187,12 @@ class DBRestore extends DBBackup @chmod($sConfigFile, 0770); // Allow overwriting the file rename($sDataDir.'/config-itop.php', $sConfigFile); @chmod($sConfigFile, 0440); // Read-only + + $aExtraFiles = $this->ListExtraFiles($sDataDir); + foreach($aExtraFiles as $sSourceFilePath => $sDestinationFilePath) { + SetupUtils::builddir(dirname($sDestinationFilePath)); + rename($sSourceFilePath, $sDestinationFilePath); + } try { SetupUtils::rrmdir($sDataDir); @@ -211,4 +217,27 @@ class DBRestore extends DBBackup $oRestoreMutex->Unlock(); } } + + /** + * List the 'extra files' found in the decompressed archive + * (i.e. files other than config-itop.php, delta.xml, itop-dump.sql or production-modules/* + * @param string $sDataDir + * @return string[] + */ + protected function ListExtraFiles(string $sDataDir) + { + $aExtraFiles = []; + $aStandardFiles = ['config-itop.php', 'itop-dump.sql', 'production-modules', 'delta.xml']; + $oDirectoryIterator = new RecursiveDirectoryIterator($sDataDir, FilesystemIterator::CURRENT_AS_FILEINFO|FilesystemIterator::SKIP_DOTS); + $oIterator = new RecursiveIteratorIterator($oDirectoryIterator); + foreach ($oIterator as $oFileInfo) + { + if (in_array($oFileInfo->getFilename(), $aStandardFiles)) continue; + if (strncmp($oFileInfo->getPathname(), $sDataDir.'/production-modules', strlen($sDataDir.'/production-modules')) == 0) continue; + + $aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir)); + } + + return $aExtraFiles; + } } diff --git a/setup/backup.class.inc.php b/setup/backup.class.inc.php index 854c7594b..a8d6f6849 100644 --- a/setup/backup.class.inc.php +++ b/setup/backup.class.inc.php @@ -247,6 +247,26 @@ class DBBackup SetupUtils::copydir($sExtraDir, $sFile); $aRet[] = $sFile; } + $aExtraFiles = MetaModel::GetModuleSetting('itop-backup', 'extra_files', []); + foreach($aExtraFiles as $sExtraFileOrDir) + { + $sExtraFullPath = APPROOT.'/'.$sExtraFileOrDir; + if (is_dir($sExtraFullPath)) + { + $sFile = $sTmpFolder.'/'.$sExtraFileOrDir; + $this->LogInfo("backup: adding directory '$sExtraFileOrDir'"); + SetupUtils::copydir($sExtraFullPath, $sFile); + $aRet[] = $sFile; + } + elseif (file_exists($sExtraFullPath)) + { + $sFile = $sTmpFolder.'/'.$sExtraFileOrDir; + $this->LogInfo("backup: adding file '$sExtraFileOrDir'"); + @mkdir(dirname($sFile), 0755, true); + copy($sExtraFullPath, $sFile); + $aRet[] = $sFile; + } + } $sDataFile = $sTmpFolder.'/itop-dump.sql'; $this->DoBackup($sDataFile); $aRet[] = $sDataFile; diff --git a/test/setup/DBBackupDataTest.php b/test/setup/DBBackupDataTest.php new file mode 100644 index 000000000..aed8fe73c --- /dev/null +++ b/test/setup/DBBackupDataTest.php @@ -0,0 +1,105 @@ +SetModuleSetting('itop-backup', 'extra_files', array_keys($aExtraFiles)); + + foreach($aExtraFiles as $sExtraFile => $bExists) + { + if ($bExists) + { + @mkdir(dirname(APPROOT.'/'.$sExtraFile), 0755, true); + file_put_contents(APPROOT.'/'.$sExtraFile, 'Hello World!'); + } + } + + $aFiles = $this->InvokeNonPublicMethod('DBBackup', 'PrepareFilesToBackup', $oBackup, [APPROOT.'/conf/production/config-itop.php', $sTmpDir]); + SetupUtils::rrmdir($sTmpDir); + $aExpectedFiles = [ + $sTmpDir.'/config-itop.php', + $sTmpDir.'/itop-dump.sql', + ]; + foreach($aExtraFiles as $sRelFile => $bExists) + { + if ($bExists) + { + $aExpectedFiles[] = $sTmpDir.'/'.$sRelFile; + } + } + sort($aFiles); + sort($aExpectedFiles); + $this->assertEquals($aFiles, $aExpectedFiles); + + // Cleanup + foreach($aExtraFiles as $sExtraFile => $bExists) + { + if ($bExists) + { + unlink(APPROOT.'/'.$sExtraFile); + } + } + } + + /** + * @dataProvider prepareFilesToBackupProvider + */ + function testRestoreListExtraFiles($aFilesToCreate, $aExpectedRelativeExtraFiles) + { + require_once(APPROOT.'/env-production/itop-backup/dbrestore.class.inc.php'); + + $sTmpDir = sys_get_temp_dir().'/testRestoreListExtraFiles-'.time(); + + foreach($aFilesToCreate as $sRelativeName) + { + $sDir = $sTmpDir.'/'.dirname($sRelativeName); + if(!is_dir($sDir)) + { + mkdir($sDir, 0755, true); + } + file_put_contents($sTmpDir.'/'.$sRelativeName, 'Hello world.'); + } + $aExpectedExtraFiles = []; + foreach($aExpectedRelativeExtraFiles as $sRelativeName) + { + $aExpectedExtraFiles[$sTmpDir.'/'.$sRelativeName] = APPROOT.'/'.$sRelativeName; + } + + $oRestore = new DBRestore(MetaModel::GetConfig()); + $aExtraFiles = $this->InvokeNonPublicMethod('DBRestore', 'ListExtraFiles', $oRestore, [$sTmpDir]); + + asort($aExtraFiles); + asort($aExpectedExtraFiles); + $this->assertEquals($aExpectedExtraFiles, $aExtraFiles); + SetupUtils::rrmdir($sTmpDir); + } + + function prepareFilesToBackupProvider() + { + return [ + 'no extra file' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml'], 'aExpectedExtraFiles' => []], + 'no extra file (2)' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml', 'production-modules/test/module.test.php'], 'aExpectedExtraFiles' => []], + 'one extra file' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml', 'production-modules/test/module.test.php', 'collectors/ldap/conf/params.local.xml'], 'aExpectedExtraFiles' => ['collectors/ldap/conf/params.local.xml']], + ]; + } + +}