From b8589b3f547047187345de0ca38b14c8657d71c7 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Tue, 5 Jul 2022 18:13:05 +0200 Subject: [PATCH 1/8] feat(api): endpoint for centreon web upgrade (#11307) * add skeleton * split interfaces * implement use case * move into repository * read updates * apply php upgrades * manage sql upgrades * check user is admin * phpdoc * refacto * use symfony finder * legacy use new repository * continue * remove request * add unit tests * take into accounts tamaz feedbacks * begin refacto * refacto sql upgrade method * rename repository and handle feedbacks * fix tests * fix * handle feedbacks * remove useless import * remove locker * handle properly exceptions * Update src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php Co-authored-by: Laurent Calvet * Update src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php Co-authored-by: Laurent Calvet --- config/packages/Centreon.yaml | 12 + config/routes/Centreon/platform.yaml | 6 + config/services.yaml | 3 + .../Infrastructure/DatabaseConnection.php | 10 + .../Repository/AbstractRepositoryDRB.php | 4 +- .../ReadUpdateRepositoryInterface.php | 34 +++ .../ReadVersionRepositoryInterface.php | 33 +++ .../WriteUpdateRepositoryInterface.php | 33 +++ .../UseCase/UpdateVersions/UpdateVersions.php | 148 ++++++++++ .../UpdateVersionsPresenterInterface.php | 29 ++ .../UpdateVersionsController.php | 71 +++++ .../UpdateVersionsPresenter.php | 31 +++ .../UpdateVersions/UpdateVersionsSchema.json | 25 ++ .../Repository/DbReadVersionRepository.php | 60 ++++ .../Repository/DbWriteUpdateRepository.php | 263 ++++++++++++++++++ .../Repository/FsReadUpdateRepository.php | 93 +++++++ .../UpdateVersions/UpdateVersionsTest.php | 94 +++++++ .../LegacyReadVersionRepositoryTest.php | 67 +++++ .../step_upgrade/process/process_step4.php | 129 ++------- 19 files changed, 1040 insertions(+), 105 deletions(-) create mode 100644 src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php create mode 100644 src/Core/Platform/Application/Repository/ReadVersionRepositoryInterface.php create mode 100644 src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsPresenterInterface.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsController.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php create mode 100644 src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsSchema.json create mode 100644 src/Core/Platform/Infrastructure/Repository/DbReadVersionRepository.php create mode 100644 src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php create mode 100644 src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php create mode 100644 tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php create mode 100644 tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index 2773911706c..18b44c439f6 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -220,6 +220,18 @@ services: class: Core\Infrastructure\Platform\Repository\FileReadPlatformRepository arguments: ['%centreon_etc_path%', '%centreon_install_path%'] + Core\Platform\Application\Repository\ReadVersionRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\DbReadVersionRepository + public: true + + Core\Platform\Application\Repository\ReadUpdateRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\FsReadUpdateRepository + public: true + + Core\Platform\Application\Repository\WriteUpdateRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\DbWriteUpdateRepository + public: true + # Monitoring resources _instanceof: Centreon\Infrastructure\Monitoring\Resource\Provider\ProviderInterface: diff --git a/config/routes/Centreon/platform.yaml b/config/routes/Centreon/platform.yaml index a521348ecbf..d77666e43f6 100644 --- a/config/routes/Centreon/platform.yaml +++ b/config/routes/Centreon/platform.yaml @@ -4,6 +4,12 @@ centreon_application_platform_getversion: controller: 'Centreon\Application\Controller\PlatformController::getVersions' condition: "request.attributes.get('version') >= 21.10" +centreon_application_platform_updateversions: + methods: PATCH + path: /platform/updates + controller: 'Core\Platform\Infrastructure\Api\UpdateVersions\UpdateVersionsController' + condition: "request.attributes.get('version') >= 22.04" + centreon_application_platformtopology_addplatformtotopology: methods: POST path: /platform/topology diff --git a/config/services.yaml b/config/services.yaml index 41975cd9de1..d1ca3a46121 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -66,6 +66,9 @@ services: decorates: router arguments: ['@.inner'] + Symfony\Component\Finder\Finder: + shared: false + # Security Security\Domain\Authentication\Interfaces\AuthenticationRepositoryInterface: diff --git a/src/Centreon/Infrastructure/DatabaseConnection.php b/src/Centreon/Infrastructure/DatabaseConnection.php index 404ada96717..39263cc0cff 100644 --- a/src/Centreon/Infrastructure/DatabaseConnection.php +++ b/src/Centreon/Infrastructure/DatabaseConnection.php @@ -91,4 +91,14 @@ public function setStorageDbName(string $storageDbName) { $this->storageDbName = $storageDbName; } + + /** + * switch connection to another database + * + * @param string $dbName + */ + public function switchToDb(string $dbName): void + { + $this->query('use ' . $dbName); + } } diff --git a/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php b/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php index c8ccf79ea23..27e68c256fd 100644 --- a/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php +++ b/src/Centreon/Infrastructure/Repository/AbstractRepositoryDRB.php @@ -48,8 +48,8 @@ class AbstractRepositoryDRB protected function translateDbName(string $request): string { return str_replace( - array(':dbstg', ':db'), - array($this->db->getStorageDbName(), $this->db->getCentreonDbName()), + [':dbstg', ':db'], + [$this->db->getStorageDbName(), $this->db->getCentreonDbName()], $request ); } diff --git a/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php b/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php new file mode 100644 index 00000000000..db999e5fa71 --- /dev/null +++ b/src/Core/Platform/Application/Repository/ReadUpdateRepositoryInterface.php @@ -0,0 +1,34 @@ +info('Updating versions'); + + try { + $currentVersion = $this->getCurrentVersionOrFail(); + + $availableUpdates = $this->getAvailableUpdatesOrFail($currentVersion); + + $this->runUpdates($availableUpdates); + } catch (\Throwable $e) { + $this->error( + $e->getMessage(), + ['trace' => $e->getTraceAsString()], + ); + + $presenter->setResponseStatus(new ErrorResponse($e->getMessage())); + + return; + } + + $presenter->setResponseStatus(new NoContentResponse()); + } + + /** + * Get current version or fail + * + * @return string + * + * @throws \Exception + */ + private function getCurrentVersionOrFail(): string + { + $this->info('Getting current version'); + + try { + $currentVersion = $this->readVersionRepository->findCurrentVersion(); + } catch (\Exception $e) { + throw new \Exception('An error occurred when retrieving current version', 0, $e); + } + + if ($currentVersion === null) { + throw new \Exception('Cannot retrieve current version'); + } + + return $currentVersion; + } + + /** + * Get available updates + * + * @param string $currentVersion + * @return string[] + */ + private function getAvailableUpdatesOrFail(string $currentVersion): array + { + try { + $this->info( + 'Getting available updates', + [ + 'current_version' => $currentVersion, + ], + ); + + return $this->readUpdateRepository->findOrderedAvailableUpdates($currentVersion); + } catch (\Throwable $e) { + throw new \Exception('An error occurred when getting available updates', 0, $e); + } + } + + /** + * Run given version updates + * + * @param string[] $versions + * + * @throws \Throwable + */ + private function runUpdates(array $versions): void + { + foreach ($versions as $version) { + try { + $this->info("Running update $version"); + $this->writeUpdateRepository->runUpdate($version); + } catch (\Throwable $e) { + throw new \Exception( + sprintf( + 'An error occurred when applying update %s (%s)', + $version, + $e->getMessage(), + ), + 0, + $e, + ); + } + } + } +} diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsPresenterInterface.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsPresenterInterface.php new file mode 100644 index 00000000000..efc7cc4a107 --- /dev/null +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsPresenterInterface.php @@ -0,0 +1,29 @@ +denyAccessUnlessGrantedForApiConfiguration(); + + /** + * @var Contact $contact + */ + $contact = $this->getUser(); + if (! $contact->isAdmin()) { + $presenter->setResponseStatus(new UnauthorizedResponse('Only admin user can perform upgrade')); + + return $presenter->show(); + } + + $this->info('Validating request body...'); + $this->validateDataSent($request, __DIR__ . '/UpdateVersionsSchema.json'); + + $useCase($presenter); + + return $presenter->show(); + } +} diff --git a/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php b/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php new file mode 100644 index 00000000000..a27dcfad745 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Api/UpdateVersions/UpdateVersionsPresenter.php @@ -0,0 +1,31 @@ +db = $db; + } + + /** + * @inheritDoc + */ + public function findCurrentVersion(): ?string + { + $currentVersion = null; + + $statement = $this->db->query( + "SELECT `value` FROM `informations` WHERE `key` = 'version'" + ); + if ($statement !== false && is_array($result = $statement->fetch(\PDO::FETCH_ASSOC))) { + $currentVersion = $result['value']; + } + + return $currentVersion; + } +} diff --git a/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php new file mode 100644 index 00000000000..3fe397f02f5 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php @@ -0,0 +1,263 @@ +db = $db; + } + + /** + * @inheritDoc + */ + public function runUpdate(string $version): void + { + $this->runMonitoringSql($version); + $this->runScript($version); + $this->runConfigurationSql($version); + $this->runPostScript($version); + $this->updateVersionInformation($version); + } + + /** + * Run sql queries on monitoring database + * + * @param string $version + */ + private function runMonitoringSql(string $version): void + { + $upgradeFilePath = self::INSTALL_DIR . '/sql/centstorage/Update-CSTG-' . $version . '.sql'; + if (is_readable($upgradeFilePath)) { + $this->db->switchToDb($this->db->getStorageDbName()); + $this->runSqlFile($upgradeFilePath); + } + } + + /** + * Run php upgrade script + * + * @param string $version + */ + private function runScript(string $version): void + { + $pearDB = $this->dependencyInjector['configuration_db']; + $pearDBO = $this->dependencyInjector['realtime_db']; + + $upgradeFilePath = self::INSTALL_DIR . '/php/Update-' . $version . '.php'; + if (is_readable($upgradeFilePath)) { + include_once $upgradeFilePath; + } + } + + /** + * Run sql queries on configuration database + * + * @param string $version + */ + private function runConfigurationSql(string $version): void + { + $upgradeFilePath = self::INSTALL_DIR . '/sql/centreon/Update-DB-' . $version . '.sql'; + if (is_readable($upgradeFilePath)) { + $this->db->switchToDb($this->db->getCentreonDbName()); + $this->runSqlFile($upgradeFilePath); + } + } + + /** + * Run php post upgrade script + * + * @param string $version + */ + private function runPostScript(string $version): void + { + $pearDB = $this->dependencyInjector['configuration_db']; + $pearDBO = $this->dependencyInjector['realtime_db']; + + $upgradeFilePath = self::INSTALL_DIR . '/php/Update-' . $version . '.post.php'; + if (is_readable($upgradeFilePath)) { + include_once $upgradeFilePath; + } + } + + /** + * Update version information + * + * @param string $version + */ + private function updateVersionInformation(string $version): void + { + $statement = $this->db->prepare( + $this->translateDbName( + "UPDATE `:db`.`informations` SET `value` = :version WHERE `key` = 'version'" + ) + ); + $statement->bindValue(':version', $version, \PDO::PARAM_STR); + $statement->execute(); + } + + /** + * Run sql file and use temporary file to store last executed line + * + * @param string $filePath + * @return void + */ + private function runSqlFile(string $filePath): void + { + set_time_limit(0); + + $fileName = basename($filePath); + $tmpFile = self::INSTALL_DIR . '/tmp/' . $fileName; + + $alreadyExecutedQueriesCount = $this->getAlreadyExecutedQueriesCount($tmpFile); + + if (is_readable($filePath)) { + $fileStream = fopen($filePath, 'r'); + if (is_resource($fileStream)) { + $query = ''; + $currentLineNumber = 0; + $executedQueriesCount = 0; + try { + while (! feof($fileStream)) { + $currentLineNumber++; + $currentLine = fgets($fileStream); + if ($currentLine && ! $this->isSqlComment($currentLine)) { + $query .= ' ' . trim($currentLine); + } + + if ($this->isSqlCompleteQuery($query)) { + $executedQueriesCount++; + if ($executedQueriesCount > $alreadyExecutedQueriesCount) { + try { + $this->executeQuery($query); + } catch (RepositoryException $e) { + throw $e; + } + + $this->writeExecutedQueriesCountInTemporaryFile($tmpFile, $executedQueriesCount); + } + $query = ''; + } + } + } catch (\Throwable $e) { + $this->error($e->getMessage(), ['trace' => $e->getTraceAsString()]); + throw $e; + } finally { + fclose($fileStream); + } + } + } + } + + /** + * Get stored executed queries count in temporary file to retrieve next query to run in case of an error occurred + * + * @param string $tmpFile + * @return int + */ + private function getAlreadyExecutedQueriesCount(string $tmpFile): int + { + $startLineNumber = 0; + if (is_readable($tmpFile)) { + $lineNumber = file_get_contents($tmpFile); + if (is_numeric($lineNumber)) { + $startLineNumber = (int) $lineNumber; + } + } + + return $startLineNumber; + } + + /** + * Write executed queries count in temporary file to retrieve upgrade when an error occurred + * + * @param string $tmpFile + * @param int $count + */ + private function writeExecutedQueriesCountInTemporaryFile(string $tmpFile, int $count): void + { + if (! file_exists($tmpFile) || is_writable($tmpFile)) { + $this->info('Writing in temporary file : ' . $tmpFile); + file_put_contents($tmpFile, $count); + } else { + $this->warning('Cannot write in temporary file : ' . $tmpFile); + } + } + + /** + * Check if a line a sql comment + * + * @param string $line + * @return bool + */ + private function isSqlComment(string $line): bool + { + return str_starts_with('--', trim($line)); + } + + /** + * Check if a query is complete (trailing semicolon) + * + * @param string $query + * @return bool + */ + private function isSqlCompleteQuery(string $query): bool + { + return ! empty(trim($query)) && preg_match('/;\s*$/', $query); + } + + /** + * Execute sql query + * + * @param string $query + * + * @throws \Exception + */ + private function executeQuery(string $query): void + { + try { + $this->db->query($query); + } catch (\Exception $e) { + throw new RepositoryException('Cannot execute query: ' . $query, 0, $e); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php new file mode 100644 index 00000000000..dc15f27f654 --- /dev/null +++ b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php @@ -0,0 +1,93 @@ +findAvailableUpdates($currentVersion); + + return $this->orderUpdates($availableUpdates); + } + + /** + * Get available updates + * + * @param string $currentVersion + * @return string[] + */ + private function findAvailableUpdates(string $currentVersion): array + { + $fileNameVersionRegex = '/Update-(?[a-zA-Z0-9\-\.]+)\.php/'; + $availableUpdates = []; + $updateFiles = $this->finder->files() + ->in(__DIR__ . '/../../../../../www/install/php') + ->name($fileNameVersionRegex); + + foreach ($updateFiles as $updateFile) { + if (preg_match($fileNameVersionRegex, $updateFile->getFilename(), $matches)) { + if (version_compare($matches['version'], $currentVersion, '>')) { + $this->info('Update version found: ' . $matches['version']); + $availableUpdates[] = $matches['version']; + } + } + } + + return $availableUpdates; + } + + /** + * Order updates + * + * @param string[] $updates + * @return string[] + */ + private function orderUpdates(array $updates): array + { + usort( + $updates, + fn (string $versionA, string $versionB) => version_compare($versionA, $versionB), + ); + + return $updates; + } +} diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php new file mode 100644 index 00000000000..a772a21169b --- /dev/null +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -0,0 +1,94 @@ +readVersionRepository = $this->createMock(ReadVersionRepositoryInterface::class); + $this->readUpdateRepository = $this->createMock(ReadUpdateRepositoryInterface::class); + $this->writeUpdateRepository = $this->createMock(WriteUpdateRepositoryInterface::class); + $this->presenter = $this->createMock(UpdateVersionsPresenterInterface::class); +}); + +it('should present an error response if current version is not found', function () { + $updateVersions = new UpdateVersions( + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->readVersionRepository + ->expects($this->once()) + ->method('findCurrentVersion') + ->willReturn(null); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Cannot retrieve current version')); + + $updateVersions($this->presenter); +}); + +it('should run found updates', function () { + $updateVersions = new UpdateVersions( + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->readVersionRepository + ->expects($this->once()) + ->method('findCurrentVersion') + ->willReturn('22.04.0'); + + $this->readUpdateRepository + ->expects($this->once()) + ->method('findOrderedAvailableUpdates') + ->with('22.04.0') + ->willReturn(['22.10.0-beta.1', '22.10.0', '22.10.1']); + + $this->writeUpdateRepository + ->expects($this->exactly(3)) + ->method('runUpdate') + ->withConsecutive( + [$this->equalTo('22.10.0-beta.1')], + [$this->equalTo('22.10.0')], + [$this->equalTo('22.10.1')], + ); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new NoContentResponse()); + + $updateVersions($this->presenter); +}); diff --git a/tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php b/tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php new file mode 100644 index 00000000000..2064430fe22 --- /dev/null +++ b/tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php @@ -0,0 +1,67 @@ +finder = $this->createMock(Finder::class); +}); + +it('should order found updates', function () { + $repository = new FsReadUpdateRepository($this->finder); + + $this->finder + ->expects($this->once()) + ->method('files') + ->willReturn($this->finder); + + $this->finder + ->expects($this->once()) + ->method('in') + ->willReturn($this->finder); + + $this->finder + ->expects($this->once()) + ->method('name') + ->willReturn( + [ + new \SplFileInfo('Update-21.10.0.php'), + new \SplFileInfo('Update-22.04.0.php'), + new \SplFileInfo('Update-22.10.11.php'), + new \SplFileInfo('Update-22.10.1.php'), + new \SplFileInfo('Update-22.10.0-beta.3.php'), + new \SplFileInfo('Update-22.10.0-alpha.1.php'), + ] + ); + + $availableUpdates = $repository->findOrderedAvailableUpdates('22.04.0'); + expect($availableUpdates)->toEqual([ + '22.10.0-alpha.1', + '22.10.0-beta.3', + '22.10.1', + '22.10.11' + ]); +}); diff --git a/www/install/step_upgrade/process/process_step4.php b/www/install/step_upgrade/process/process_step4.php index 308c61a21af..5f9f6b94a93 100644 --- a/www/install/step_upgrade/process/process_step4.php +++ b/www/install/step_upgrade/process/process_step4.php @@ -1,128 +1,51 @@ . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Linking this program statically or dynamically with other modules is making a - * combined work based on this program. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. + * http://www.apache.org/licenses/LICENSE-2.0 * - * As a special exception, the copyright holders of this program give Centreon - * permission to link this program with independent modules to produce an executable, - * regardless of the license terms of these independent modules, and to copy and - * distribute the resulting executable under terms of Centreon choice, provided that - * Centreon also meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module which is not - * derived from this program. If you modify this program, you may extend this - * exception to your version of the program, but you are not obliged to do so. If you - * do not wish to do so, delete this exception statement from your version. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * * For more information : contact@centreon.com * */ session_start(); -require_once realpath(dirname(__FILE__) . "/../../../../config/centreon.config.php"); -require_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php'; -require_once '../../steps/functions.php'; +require_once __DIR__ . '/../../../../bootstrap.php'; +require_once __DIR__ . '/../../../class/centreonDB.class.php'; +require_once __DIR__ . '/../../steps/functions.php'; + +use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; +use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; $current = $_POST['current']; $next = $_POST['next']; $status = 0; -/** - * Variables for upgrade scripts - */ +$kernel = \App\Kernel::createForWeb(); +$upgradeWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); try { - $pearDB = new CentreonDB('centreon', 3); - $pearDBO = new CentreonDB('centstorage', 3); -} catch (Exception $e) { + $upgradeWriteRepository->runUpdate($next); +} catch (\Throwable $e) { exitUpgradeProcess(1, $current, $next, $e->getMessage()); } -/** - * Upgrade storage sql - */ -$storageSql = '../../sql/centstorage/Update-CSTG-' . $next . '.sql'; -if (is_file($storageSql)) { - $result = splitQueries($storageSql, ';', $pearDBO, '../../tmp/Update-CSTG-' . $next); - if ("0" != $result) { - exitUpgradeProcess(1, $current, $next, $result); - } -} - -/** - * Pre upgrade PHP - */ -$prePhp = '../../php/Update-' . $next . '.php'; -if (is_file($prePhp)) { - try { - include_once $prePhp; - } catch (Exception $e) { - exitUpgradeProcess(1, $current, $next, $e->getMessage()); - } -} - -/** - * Upgrade configuration sql - */ -$confSql = '../../sql/centreon/Update-DB-' . $next . '.sql'; -if (is_file($confSql)) { - $result = splitQueries($confSql, ';', $pearDB, '../../tmp/Update-DB-' . $next); - if ("0" != $result) { - exitUpgradeProcess(1, $current, $next, $result); - } -} - -/** - * Post upgrade PHP - */ -$postPhp = '../../php/Update-' . $next . '.post.php'; -if (is_file($postPhp)) { - try { - include_once $postPhp; - } catch (Exception $e) { - exitUpgradeProcess(1, $current, $next, $e->getMessage()); - } -} - -/** - * Update version in database. - */ -$res = $pearDB->prepare("UPDATE `informations` SET `value` = ? WHERE `key` = 'version'"); -$res->execute(array($next)); $current = $next; -/* -** To find the next version that we should update to, we will look in -** the www/install/php directory where all PHP update scripts are -** stored. We will extract the target version from the filename and find -** the closest version to the current version. -*/ -$next = ''; -if ($handle = opendir('../../php')) { - while (false !== ($file = readdir($handle))) { - if (preg_match('/Update-([a-zA-Z0-9\-\.]+)\.php/', $file, $matches)) { - if ((version_compare($current, $matches[1]) < 0) && - (empty($next) || (version_compare($matches[1], $next) < 0))) { - $next = $matches[1]; - } - } - } - closedir($handle); -} +$upgradeReadRepository = $kernel->getContainer()->get(ReadUpdateRepositoryInterface::class); +$availableUpdates = $upgradeReadRepository->getOrderedAvailableUpdates($current); +$next = empty($availableUpdates) ? '' : array_shift($availableUpdates); + $_SESSION['CURRENT_VERSION'] = $current; $okMsg = "OK"; + exitUpgradeProcess($status, $current, $next, $okMsg); From 2efc0e83bbfc3704fcf95a429659d8e47be68beb Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Thu, 7 Jul 2022 14:33:01 +0200 Subject: [PATCH 2/8] feat(api): add update locker (#11341) Refs: MON-12296 --- composer.json | 1 + composer.lock | 83 ++++++++++++++++++- config/packages/Centreon.yaml | 4 + lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 21 +++++ .../Repository/UpdateLockerException.php | 44 ++++++++++ .../UpdateLockerRepositoryInterface.php | 42 ++++++++++ .../UseCase/UpdateVersions/UpdateVersions.php | 45 +++++++--- .../UpdateVersionsException.php | 78 +++++++++++++++++ .../SymfonyUpdateLockerRepository.php | 79 ++++++++++++++++++ .../UpdateVersions/UpdateVersionsTest.php | 35 ++++++++ .../step_upgrade/process/process_step4.php | 19 ++++- 11 files changed, 433 insertions(+), 18 deletions(-) create mode 100644 src/Core/Platform/Application/Repository/UpdateLockerException.php create mode 100644 src/Core/Platform/Application/Repository/UpdateLockerRepositoryInterface.php create mode 100644 src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php create mode 100644 src/Core/Platform/Infrastructure/Repository/SymfonyUpdateLockerRepository.php diff --git a/composer.json b/composer.json index c7c1a574bcd..e87c05a183d 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,7 @@ "symfony/framework-bundle": "5.4.*", "symfony/http-client": "5.4.*", "symfony/http-kernel": "5.4.*", + "symfony/lock": "5.4.*", "symfony/maker-bundle": "^1.11", "symfony/monolog-bundle": "^3.7", "symfony/options-resolver": "5.4.*", diff --git a/composer.lock b/composer.lock index 23c9b05bd79..6dc53f71f39 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94134d5a5dc2cb311e57863a9f0dafd8", + "content-hash": "1c4ccf2b3d1f768dfd30298637120a27", "packages": [ { "name": "beberlei/assert", @@ -3623,6 +3623,85 @@ ], "time": "2022-05-27T07:09:08+00:00" }, + { + "name": "symfony/lock", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "41a308008d92d30cae5615d903c4d46d95932eea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/41a308008d92d30cae5615d903c4d46d95932eea", + "reference": "41a308008d92d30cae5615d903c4d46d95932eea", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/dbal": "<2.13" + }, + "require-dev": { + "doctrine/dbal": "^2.13|^3.0", + "predis/predis": "~1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Lock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Derussé", + "email": "jeremy@derusse.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", + "homepage": "https://symfony.com", + "keywords": [ + "cas", + "flock", + "locking", + "mutex", + "redlock", + "semaphore" + ], + "support": { + "source": "https://github.com/symfony/lock/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-09T13:29:56+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.43.0", @@ -10885,5 +10964,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index 18b44c439f6..55d9eede4dd 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -228,6 +228,10 @@ services: class: Core\Platform\Infrastructure\Repository\FsReadUpdateRepository public: true + Core\Platform\Application\Repository\UpdateLockerRepositoryInterface: + class: Core\Platform\Infrastructure\Repository\SymfonyUpdateLockerRepository + public: true + Core\Platform\Application\Repository\WriteUpdateRepositoryInterface: class: Core\Platform\Infrastructure\Repository\DbWriteUpdateRepository public: true diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index 50b7fbbb1a3..e19e9a2dd5b 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16916,3 +16916,24 @@ msgstr "Une erreur s'est produite lors de la récupération des catégories d'h msgid "Warning, maximum size exceeded for input '%s' (max: %d), it will be truncated upon saving" msgstr "Attention, taille maximale dépassée pour le champ '%s' (max: %d), il sera tronqué à l'enregistrement" + +msgid "Update already in progress" +msgstr "Une mise à jour est déjà en cours" + +msgid "An error occurred when retrieving current version" +msgstr "Une erreur s'est produite lors de la récupération de la version actuelle" + +msgid "Cannot retrieve current version" +msgstr "La version actuelle n'a pas pu être trouvée" + +msgid "An error occurred when getting available updates" +msgstr "Une erreur s'est produite lors de la récupération des mises à jour disponibles" + +msgid "An error occurred when applying update %s (%s)" +msgstr "Une erreur s'est produite lors de l'application de la mise à jour %s (%s)" + +msgid "Error while locking update process" +msgstr "Erreur lors du verrouillage du processus de mise à jour" + +msgid "'Error while unlocking update process" +msgstr "Erreur lors du déverrouillage du processus de mise à jour" diff --git a/src/Core/Platform/Application/Repository/UpdateLockerException.php b/src/Core/Platform/Application/Repository/UpdateLockerException.php new file mode 100644 index 00000000000..57fbfff71e3 --- /dev/null +++ b/src/Core/Platform/Application/Repository/UpdateLockerException.php @@ -0,0 +1,44 @@ +info('Updating versions'); try { + $this->lockUpdate(); + $currentVersion = $this->getCurrentVersionOrFail(); $availableUpdates = $this->getAvailableUpdatesOrFail($currentVersion); $this->runUpdates($availableUpdates); + + $this->unlockUpdate(); } catch (\Throwable $e) { $this->error( $e->getMessage(), @@ -73,6 +80,28 @@ public function __invoke( $presenter->setResponseStatus(new NoContentResponse()); } + /** + * Lock update process + */ + private function lockUpdate(): void + { + $this->info('Locking centreon update process...'); + + if (!$this->updateLocker->lock()) { + throw UpdateVersionsException::updateAlreadyInProgress(); + } + } + + /** + * Unlock update process + */ + private function unlockUpdate(): void + { + $this->info('Unlocking centreon update process...'); + + $this->updateLocker->unlock(); + } + /** * Get current version or fail * @@ -87,11 +116,11 @@ private function getCurrentVersionOrFail(): string try { $currentVersion = $this->readVersionRepository->findCurrentVersion(); } catch (\Exception $e) { - throw new \Exception('An error occurred when retrieving current version', 0, $e); + throw UpdateVersionsException::errorWhenRetrievingCurrentVersion($e); } if ($currentVersion === null) { - throw new \Exception('Cannot retrieve current version'); + throw UpdateVersionsException::cannotRetrieveCurrentVersion(); } return $currentVersion; @@ -115,7 +144,7 @@ private function getAvailableUpdatesOrFail(string $currentVersion): array return $this->readUpdateRepository->findOrderedAvailableUpdates($currentVersion); } catch (\Throwable $e) { - throw new \Exception('An error occurred when getting available updates', 0, $e); + throw UpdateVersionsException::errorWhenRetrievingAvailableUpdates($e); } } @@ -133,15 +162,7 @@ private function runUpdates(array $versions): void $this->info("Running update $version"); $this->writeUpdateRepository->runUpdate($version); } catch (\Throwable $e) { - throw new \Exception( - sprintf( - 'An error occurred when applying update %s (%s)', - $version, - $e->getMessage(), - ), - 0, - $e, - ); + throw UpdateVersionsException::errorWhenApplyingUpdate($version, $e->getMessage(), $e); } } } diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php new file mode 100644 index 00000000000..98edafd9779 --- /dev/null +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php @@ -0,0 +1,78 @@ +lock = $lockFactory->createLock(self::LOCK_NAME); + } + + /** + * @inheritDoc + */ + public function lock(): bool + { + $this->info('Locking centreon update process on filesystem...'); + + try { + return $this->lock->acquire(); + } catch (\Throwable $e) { + throw UpdateLockerException::errorWhileLockingUpdate($e); + } + } + + /** + * @inheritDoc + */ + public function unlock(): void + { + $this->info('Unlocking centreon update process from filesystem...'); + + try { + $this->lock->release(); + } catch (\Throwable $e) { + throw UpdateLockerException::errorWhileUnlockingUpdate($e); + } + } +} diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php index a772a21169b..2efa5aa9ae0 100644 --- a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -25,6 +25,7 @@ use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersions; use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsPresenterInterface; +use Core\Platform\Application\Repository\UpdateLockerRepositoryInterface; use Core\Platform\Application\Repository\ReadVersionRepositoryInterface; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; @@ -32,19 +33,47 @@ use Core\Application\Common\UseCase\NoContentResponse; beforeEach(function () { + $this->updateLockerRepository = $this->createMock(UpdateLockerRepositoryInterface::class); $this->readVersionRepository = $this->createMock(ReadVersionRepositoryInterface::class); $this->readUpdateRepository = $this->createMock(ReadUpdateRepositoryInterface::class); $this->writeUpdateRepository = $this->createMock(WriteUpdateRepositoryInterface::class); $this->presenter = $this->createMock(UpdateVersionsPresenterInterface::class); }); +it('should stop update process when an other update is already started', function () { + $updateVersions = new UpdateVersions( + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(false); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Update already in progress')); + + $updateVersions($this->presenter); +}); + it('should present an error response if current version is not found', function () { $updateVersions = new UpdateVersions( + $this->updateLockerRepository, $this->readVersionRepository, $this->readUpdateRepository, $this->writeUpdateRepository, ); + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(true); + $this->readVersionRepository ->expects($this->once()) ->method('findCurrentVersion') @@ -60,11 +89,17 @@ it('should run found updates', function () { $updateVersions = new UpdateVersions( + $this->updateLockerRepository, $this->readVersionRepository, $this->readUpdateRepository, $this->writeUpdateRepository, ); + $this->updateLockerRepository + ->expects($this->once()) + ->method('lock') + ->willReturn(true); + $this->readVersionRepository ->expects($this->once()) ->method('findCurrentVersion') diff --git a/www/install/step_upgrade/process/process_step4.php b/www/install/step_upgrade/process/process_step4.php index 5f9f6b94a93..a664ed2b5d5 100644 --- a/www/install/step_upgrade/process/process_step4.php +++ b/www/install/step_upgrade/process/process_step4.php @@ -24,25 +24,36 @@ require_once __DIR__ . '/../../../class/centreonDB.class.php'; require_once __DIR__ . '/../../steps/functions.php'; +use Core\Platform\Application\Repository\UpdateLockerRepositoryInterface; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; +use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsException; $current = $_POST['current']; $next = $_POST['next']; $status = 0; $kernel = \App\Kernel::createForWeb(); -$upgradeWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); + +$updateLockerRepository = $kernel->getContainer()->get(UpdateLockerRepositoryInterface::class); +$updateWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); + try { - $upgradeWriteRepository->runUpdate($next); + if (! $updateLockerRepository->lock()) { + throw UpdateVersionsException::updateAlreadyInProgress(); + } + + $updateWriteRepository->runUpdate($next); + + $updateLockerRepository->unlock(); } catch (\Throwable $e) { exitUpgradeProcess(1, $current, $next, $e->getMessage()); } $current = $next; -$upgradeReadRepository = $kernel->getContainer()->get(ReadUpdateRepositoryInterface::class); -$availableUpdates = $upgradeReadRepository->getOrderedAvailableUpdates($current); +$updateReadRepository = $kernel->getContainer()->get(ReadUpdateRepositoryInterface::class); +$availableUpdates = $updateReadRepository->getOrderedAvailableUpdates($current); $next = empty($availableUpdates) ? '' : array_shift($availableUpdates); $_SESSION['CURRENT_VERSION'] = $current; From 1f22eec0cc41ea9ca17da2417d0de624369ba278 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 8 Jul 2022 10:55:17 +0200 Subject: [PATCH 3/8] enh(api): remove install directory after update (#11345) Refs: MON-12296 --- config/packages/Centreon.yaml | 1 + lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 5 +- .../WriteUpdateRepositoryInterface.php | 7 ++ .../UseCase/UpdateVersions/UpdateVersions.php | 20 ++++++ .../UpdateVersionsException.php | 9 +++ .../Repository/DbWriteUpdateRepository.php | 70 +++++++++++++++++-- .../Repository/FsReadUpdateRepository.php | 12 +++- .../UpdateVersions/UpdateVersionsTest.php | 4 +- ...est.php => FsReadUpdateRepositoryTest.php} | 21 +++++- .../step_upgrade/process/process_step5.php | 58 +++++---------- 10 files changed, 153 insertions(+), 54 deletions(-) rename tests/php/Core/Platform/Infrastructure/Repository/{LegacyReadVersionRepositoryTest.php => FsReadUpdateRepositoryTest.php} (73%) diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index 55d9eede4dd..151e467dfd8 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -234,6 +234,7 @@ services: Core\Platform\Application\Repository\WriteUpdateRepositoryInterface: class: Core\Platform\Infrastructure\Repository\DbWriteUpdateRepository + arguments: ['%centreon_var_lib%', '%centreon_install_path%'] public: true # Monitoring resources diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index e19e9a2dd5b..33965c90d90 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16935,5 +16935,8 @@ msgstr "Une erreur s'est produite lors de l'application de la mise à jour %s (% msgid "Error while locking update process" msgstr "Erreur lors du verrouillage du processus de mise à jour" -msgid "'Error while unlocking update process" +msgid "Error while unlocking update process" msgstr "Erreur lors du déverrouillage du processus de mise à jour" + +msgid "An error occurred when applying post update actions" +msgstr "Une erreur s'est produite lors de l'application des actions postérieures à la mise à jour" diff --git a/src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php b/src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php index 458557f07f0..982b32fc101 100644 --- a/src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php +++ b/src/Core/Platform/Application/Repository/WriteUpdateRepositoryInterface.php @@ -30,4 +30,11 @@ interface WriteUpdateRepositoryInterface * @param string $version */ public function runUpdate(string $version): void; + + /** + * Run post update actions + * + * @param string $currentVersion + */ + public function runPostUpdate(string $currentVersion): void; } diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php index b8c1daf8861..181fa50f51e 100644 --- a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php @@ -66,6 +66,8 @@ public function __invoke( $this->runUpdates($availableUpdates); $this->unlockUpdate(); + + $this->runPostUpdate($this->getCurrentVersionOrFail()); } catch (\Throwable $e) { $this->error( $e->getMessage(), @@ -166,4 +168,22 @@ private function runUpdates(array $versions): void } } } + + /** + * Run post update actions + * + * @param string $currentVersion + * + * @throws UpdateVersionsException + */ + private function runPostUpdate(string $currentVersion): void + { + $this->info("Running post update actions"); + + try { + $this->writeUpdateRepository->runPostUpdate($currentVersion); + } catch (\Throwable $e) { + throw UpdateVersionsException::errorWhenApplyingPostUpdate($e); + } + } } diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php index 98edafd9779..58045eed72c 100644 --- a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php @@ -75,4 +75,13 @@ public static function errorWhenApplyingUpdate( $e ); } + + /** + * @param \Throwable $e + * @return self + */ + public static function errorWhenApplyingPostUpdate(\Throwable $e): self + { + return new self(_('An error occurred when applying post update actions'), 0, $e); + } } diff --git a/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php index 3fe397f02f5..1255ee9ecc8 100644 --- a/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php +++ b/src/Core/Platform/Infrastructure/Repository/DbWriteUpdateRepository.php @@ -28,21 +28,26 @@ use Centreon\Infrastructure\DatabaseConnection; use Centreon\Infrastructure\Repository\AbstractRepositoryDRB; use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; +use Symfony\Component\Filesystem\Filesystem; use Centreon\Domain\Repository\RepositoryException; class DbWriteUpdateRepository extends AbstractRepositoryDRB implements WriteUpdateRepositoryInterface { use LoggerTrait; - private const INSTALL_DIR = __DIR__ . '/../../../../../www/install'; - /** + * @param string $libDir + * @param string $installDir * @param Container $dependencyInjector * @param DatabaseConnection $db + * @param Filesystem $filesystem */ public function __construct( + private string $libDir, + private string $installDir, private Container $dependencyInjector, DatabaseConnection $db, + private Filesystem $filesystem, ) { $this->db = $db; } @@ -59,6 +64,57 @@ public function runUpdate(string $version): void $this->updateVersionInformation($version); } + /** + * @inheritDoc + */ + public function runPostUpdate(string $currentVersion): void + { + if (! $this->filesystem->exists($this->installDir)) { + return; + } + + $this->backupInstallDirectory($currentVersion); + $this->removeInstallDirectory(); + } + + /** + * Backup installation directory + * + * @param string $currentVersion + */ + private function backupInstallDirectory(string $currentVersion): void + { + $backupDirectory = $this->libDir . '/installs/install-' . $currentVersion . '-' . date('Ymd_His'); + + $this->info( + "Backing up installation directory", + [ + 'source' => $this->installDir, + 'destination' => $backupDirectory, + ], + ); + + $this->filesystem->mirror( + $this->installDir, + $backupDirectory, + ); + } + + /** + * Remove installation directory + */ + private function removeInstallDirectory(): void + { + $this->info( + "Removing installation directory", + [ + 'installation_directory' => $this->installDir, + ], + ); + + $this->filesystem->remove($this->installDir); + } + /** * Run sql queries on monitoring database * @@ -66,7 +122,7 @@ public function runUpdate(string $version): void */ private function runMonitoringSql(string $version): void { - $upgradeFilePath = self::INSTALL_DIR . '/sql/centstorage/Update-CSTG-' . $version . '.sql'; + $upgradeFilePath = $this->installDir . '/sql/centstorage/Update-CSTG-' . $version . '.sql'; if (is_readable($upgradeFilePath)) { $this->db->switchToDb($this->db->getStorageDbName()); $this->runSqlFile($upgradeFilePath); @@ -83,7 +139,7 @@ private function runScript(string $version): void $pearDB = $this->dependencyInjector['configuration_db']; $pearDBO = $this->dependencyInjector['realtime_db']; - $upgradeFilePath = self::INSTALL_DIR . '/php/Update-' . $version . '.php'; + $upgradeFilePath = $this->installDir . '/php/Update-' . $version . '.php'; if (is_readable($upgradeFilePath)) { include_once $upgradeFilePath; } @@ -96,7 +152,7 @@ private function runScript(string $version): void */ private function runConfigurationSql(string $version): void { - $upgradeFilePath = self::INSTALL_DIR . '/sql/centreon/Update-DB-' . $version . '.sql'; + $upgradeFilePath = $this->installDir . '/sql/centreon/Update-DB-' . $version . '.sql'; if (is_readable($upgradeFilePath)) { $this->db->switchToDb($this->db->getCentreonDbName()); $this->runSqlFile($upgradeFilePath); @@ -113,7 +169,7 @@ private function runPostScript(string $version): void $pearDB = $this->dependencyInjector['configuration_db']; $pearDBO = $this->dependencyInjector['realtime_db']; - $upgradeFilePath = self::INSTALL_DIR . '/php/Update-' . $version . '.post.php'; + $upgradeFilePath = $this->installDir . '/php/Update-' . $version . '.post.php'; if (is_readable($upgradeFilePath)) { include_once $upgradeFilePath; } @@ -146,7 +202,7 @@ private function runSqlFile(string $filePath): void set_time_limit(0); $fileName = basename($filePath); - $tmpFile = self::INSTALL_DIR . '/tmp/' . $fileName; + $tmpFile = $this->installDir . '/tmp/' . $fileName; $alreadyExecutedQueriesCount = $this->getAlreadyExecutedQueriesCount($tmpFile); diff --git a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php index dc15f27f654..d6c44956814 100644 --- a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php +++ b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php @@ -25,16 +25,21 @@ use Centreon\Domain\Log\LoggerTrait; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; class FsReadUpdateRepository implements ReadUpdateRepositoryInterface { use LoggerTrait; + private const INSTALL_DIR = __DIR__ . '/../../../../../www/install'; + /** + * @param Filesystem $filesystem * @param Finder $finder */ public function __construct( + private Filesystem $filesystem, private Finder $finder, ) { } @@ -57,10 +62,15 @@ public function findOrderedAvailableUpdates(string $currentVersion): array */ private function findAvailableUpdates(string $currentVersion): array { + if (! $this->filesystem->exists(self::INSTALL_DIR)) { + return []; + } + $fileNameVersionRegex = '/Update-(?[a-zA-Z0-9\-\.]+)\.php/'; $availableUpdates = []; + $updateFiles = $this->finder->files() - ->in(__DIR__ . '/../../../../../www/install/php') + ->in(self::INSTALL_DIR) ->name($fileNameVersionRegex); foreach ($updateFiles as $updateFile) { diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php index 2efa5aa9ae0..4b402bff1b4 100644 --- a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -101,9 +101,9 @@ ->willReturn(true); $this->readVersionRepository - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('findCurrentVersion') - ->willReturn('22.04.0'); + ->will($this->onConsecutiveCalls('22.04.0', '22.10.1')); $this->readUpdateRepository ->expects($this->once()) diff --git a/tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php similarity index 73% rename from tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php rename to tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php index 2064430fe22..34c261621c2 100644 --- a/tests/php/Core/Platform/Infrastructure/Repository/LegacyReadVersionRepositoryTest.php +++ b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php @@ -24,14 +24,33 @@ namespace Tests\Core\Platform\Infrastructure\Repository; use Core\Platform\Infrastructure\Repository\FsReadUpdateRepository; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; beforeEach(function () { + $this->filesystem = $this->createMock(Filesystem::class); $this->finder = $this->createMock(Finder::class); }); +it('should not find updates if install directory does not exist', function () { + $repository = new FsReadUpdateRepository($this->filesystem, $this->finder); + + $this->filesystem + ->expects($this->once()) + ->method('exists') + ->willReturn(false); + + $availableUpdates = $repository->findOrderedAvailableUpdates('22.04.0'); + expect($availableUpdates)->toEqual([]); +}); + it('should order found updates', function () { - $repository = new FsReadUpdateRepository($this->finder); + $repository = new FsReadUpdateRepository($this->filesystem, $this->finder); + + $this->filesystem + ->expects($this->once()) + ->method('exists') + ->willReturn(true); $this->finder ->expects($this->once()) diff --git a/www/install/step_upgrade/process/process_step5.php b/www/install/step_upgrade/process/process_step5.php index df5d79e2174..c4a723a14f2 100644 --- a/www/install/step_upgrade/process/process_step5.php +++ b/www/install/step_upgrade/process/process_step5.php @@ -37,44 +37,15 @@ require_once __DIR__ . '/../../../../bootstrap.php'; require_once '../../steps/functions.php'; -function recurseRmdir($dir) -{ - $files = array_diff(scandir($dir), array('.', '..')); - foreach ($files as $file) { - (is_dir("$dir/$file")) ? recurseRmdir("$dir/$file") : unlink("$dir/$file"); - } - return rmdir($dir); -} - -function recurseCopy($source, $dest) -{ - if (is_link($source)) { - return symlink(readlink($source), $dest); - } +use Core\Platform\Application\Repository\WriteUpdateRepositoryInterface; +use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsException; - if (is_file($source)) { - return copy($source, $dest); - } - - if (!is_dir($dest)) { - mkdir($dest); - } +$kernel = \App\Kernel::createForWeb(); - $dir = dir($source); - while (false !== $entry = $dir->read()) { - if ($entry == '.' || $entry == '..') { - continue; - } - - recurseCopy("$source/$entry", "$dest/$entry"); - } - - $dir->close(); - return true; -} +$updateWriteRepository = $kernel->getContainer()->get(WriteUpdateRepositoryInterface::class); $parameters = filter_input_array(INPUT_POST); -$current = filter_var($_POST['current'] ?? "step 5", FILTER_SANITIZE_STRING); +$current = filter_var($_POST['current'] ?? "step 5", FILTER_SANITIZE_FULL_SPECIAL_CHARS); if ($parameters) { if ((int)$parameters["send_statistics"] === 1) { @@ -88,16 +59,19 @@ function recurseCopy($source, $dest) $db->query($query); } -$name = 'install-' . $_SESSION['CURRENT_VERSION'] . '-' . date('Ymd_His'); -$completeName = _CENTREON_VARLIB_ . '/installs/' . $name; -$sourceInstallDir = str_replace('step_upgrade', '', realpath(dirname(__FILE__) . '/../')); - try { - if (recurseCopy($sourceInstallDir, $completeName)) { - recurseRmdir($sourceInstallDir); + if (!isset($_SESSION['CURRENT_VERSION']) || ! preg_match('/^\d+\.\d+\.\d+/', $_SESSION['CURRENT_VERSION'])) { + throw new \Exception('Cannot get current version'); } -} catch (Exception $e) { - exitUpgradeProcess(1, $current, '', $e->getMessage()); + + $updateWriteRepository->runPostUpdate($_SESSION['CURRENT_VERSION']); +} catch (\Throwable $e) { + exitUpgradeProcess( + 1, + $current, + '', + UpdateVersionsException::errorWhenApplyingPostUpdate($e)->getMessage() + ); } session_destroy(); From 0ba7cd1867926b8ccb8227f382d47a960213fd08 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 8 Jul 2022 16:46:13 +0200 Subject: [PATCH 4/8] enh(api): add doc and api test for wep update api (#11351) Refs: MON-12296 --- config/packages/Centreon.yaml | 2 + doc/API/centreon-api-v22.10.yaml | 2 + doc/API/v22.10/Administration/updates.yaml | 30 ++++++++++++ lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 3 ++ .../Repository/UpdateNotFoundException.php | 36 ++++++++++++++ .../UseCase/UpdateVersions/UpdateVersions.php | 13 +++++ .../Repository/FsReadUpdateRepository.php | 12 +++-- tests/api/Context/PlatformUpdateContext.php | 48 +++++++++++++++++++ tests/api/behat.yml | 4 ++ tests/api/features/PlatformUpdate.feature | 41 ++++++++++++++++ .../Repository/FsReadUpdateRepositoryTest.php | 13 +++-- 11 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 doc/API/v22.10/Administration/updates.yaml create mode 100644 src/Core/Platform/Application/Repository/UpdateNotFoundException.php create mode 100644 tests/api/Context/PlatformUpdateContext.php create mode 100644 tests/api/features/PlatformUpdate.feature diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index 151e467dfd8..c90074e86d6 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -226,6 +226,8 @@ services: Core\Platform\Application\Repository\ReadUpdateRepositoryInterface: class: Core\Platform\Infrastructure\Repository\FsReadUpdateRepository + arguments: + $installDir: '%centreon_install_path%' public: true Core\Platform\Application\Repository\UpdateLockerRepositoryInterface: diff --git a/doc/API/centreon-api-v22.10.yaml b/doc/API/centreon-api-v22.10.yaml index 9f9f1ef9293..00b403076f0 100644 --- a/doc/API/centreon-api-v22.10.yaml +++ b/doc/API/centreon-api-v22.10.yaml @@ -3582,6 +3582,8 @@ paths: moduleName: type: object $ref: '#/components/schemas/Platform.Versions' + /platform/updates: + $ref: "./v22.10/Administration/updates.yaml" /platform/installation/status: get: tags: diff --git a/doc/API/v22.10/Administration/updates.yaml b/doc/API/v22.10/Administration/updates.yaml new file mode 100644 index 00000000000..4f6fa883d8b --- /dev/null +++ b/doc/API/v22.10/Administration/updates.yaml @@ -0,0 +1,30 @@ +--- +patch: + tags: + - Platform + summary: "Update Centreon web" + description: | + Update Centreon web component + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + components: + type: array + items: + type: object + properties: + name: + type: string + enum: [ centreon-web ] + responses: + 204: + description: "Platform updated" + 404: + description: "Updates not found" + 500: + $ref: "../../centreon-api-v22.10.yaml#/components/responses/InternalServerError" +... \ No newline at end of file diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index 33965c90d90..d3e1f674224 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16940,3 +16940,6 @@ msgstr "Erreur lors du déverrouillage du processus de mise à jour" msgid "An error occurred when applying post update actions" msgstr "Une erreur s'est produite lors de l'application des actions postérieures à la mise à jour" + +msgid "Updates not found" +msgstr "Les mises à jour n'ont pas été trouvées" diff --git a/src/Core/Platform/Application/Repository/UpdateNotFoundException.php b/src/Core/Platform/Application/Repository/UpdateNotFoundException.php new file mode 100644 index 00000000000..3ffc8952f75 --- /dev/null +++ b/src/Core/Platform/Application/Repository/UpdateNotFoundException.php @@ -0,0 +1,36 @@ +unlockUpdate(); $this->runPostUpdate($this->getCurrentVersionOrFail()); + } catch (UpdateNotFoundException $e) { + $this->error( + $e->getMessage(), + ['trace' => $e->getTraceAsString()], + ); + + $presenter->setResponseStatus(new NotFoundResponse('Updates')); + + return; } catch (\Throwable $e) { $this->error( $e->getMessage(), @@ -145,6 +156,8 @@ private function getAvailableUpdatesOrFail(string $currentVersion): array ); return $this->readUpdateRepository->findOrderedAvailableUpdates($currentVersion); + } catch (UpdateNotFoundException $e) { + throw $e; } catch (\Throwable $e) { throw UpdateVersionsException::errorWhenRetrievingAvailableUpdates($e); } diff --git a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php index d6c44956814..8c0a7916698 100644 --- a/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php +++ b/src/Core/Platform/Infrastructure/Repository/FsReadUpdateRepository.php @@ -25,6 +25,7 @@ use Centreon\Domain\Log\LoggerTrait; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; +use Core\Platform\Application\Repository\UpdateNotFoundException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; @@ -32,13 +33,13 @@ class FsReadUpdateRepository implements ReadUpdateRepositoryInterface { use LoggerTrait; - private const INSTALL_DIR = __DIR__ . '/../../../../../www/install'; - /** + * @param string $installDir * @param Filesystem $filesystem * @param Finder $finder */ public function __construct( + private string $installDir, private Filesystem $filesystem, private Finder $finder, ) { @@ -62,15 +63,16 @@ public function findOrderedAvailableUpdates(string $currentVersion): array */ private function findAvailableUpdates(string $currentVersion): array { - if (! $this->filesystem->exists(self::INSTALL_DIR)) { - return []; + if (! $this->filesystem->exists($this->installDir)) { + $this->error('Install directory not found on filesystem: ' . $this->installDir); + throw UpdateNotFoundException::updatesNotFound(); } $fileNameVersionRegex = '/Update-(?[a-zA-Z0-9\-\.]+)\.php/'; $availableUpdates = []; $updateFiles = $this->finder->files() - ->in(self::INSTALL_DIR) + ->in($this->installDir) ->name($fileNameVersionRegex); foreach ($updateFiles as $updateFile) { diff --git a/tests/api/Context/PlatformUpdateContext.php b/tests/api/Context/PlatformUpdateContext.php new file mode 100644 index 00000000000..842bd5c0379 --- /dev/null +++ b/tests/api/Context/PlatformUpdateContext.php @@ -0,0 +1,48 @@ +getContainer()->execute( + 'mkdir -p /usr/share/centreon/www/install/php', + 'web' + ); + $this->getContainer()->execute( + "sh -c 'echo \" /usr/share/centreon/www/install/php/Update-99.99.99.php'", + 'web' + ); + $this->getContainer()->execute( + 'chown -R apache. /usr/share/centreon/www/install', + 'web' + ); + } +} diff --git a/tests/api/behat.yml b/tests/api/behat.yml index fba023aa71d..c33fa3772c7 100644 --- a/tests/api/behat.yml +++ b/tests/api/behat.yml @@ -76,6 +76,10 @@ default: paths: [ "%paths.base%/features/PlatformInformation.feature" ] contexts: - Centreon\Test\Api\Context\PlatformInformationContext + platform_update: + paths: [ "%paths.base%/features/PlatformUpdate.feature" ] + contexts: + - Centreon\Test\Api\Context\PlatformUpdateContext host_groups: paths: [ "%paths.base%/features/HostGroup.feature" ] contexts: diff --git a/tests/api/features/PlatformUpdate.feature b/tests/api/features/PlatformUpdate.feature new file mode 100644 index 00000000000..624d875699d --- /dev/null +++ b/tests/api/features/PlatformUpdate.feature @@ -0,0 +1,41 @@ +Feature: + In order to maintain easily centreon platform + As a user + I want to update centreon web using api + + Background: + Given a running instance of Centreon Web API + And the endpoints are described in Centreon Web API documentation + + Scenario: Update platform information + Given I am logged in + + When an update is available + And I send a PATCH request to '/api/latest/platform/updates' with body: + """ + { + "components": [ + { + "name": "centreon-web" + } + ] + } + """ + Then the response code should be "204" + + When I send a GET request to '/api/latest/platform/versions' + Then the response code should be "200" + And the JSON node "web.version" should be equal to the string "99.99.99" + + When I send a PATCH request to '/api/latest/platform/updates' with body: + """ + { + "components": [ + { + "name": "centreon-web" + } + ] + } + """ + Then the response code should be "404" + And the JSON node "message" should be equal to the string "Updates not found" \ No newline at end of file diff --git a/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php index 34c261621c2..16682e3ea3c 100644 --- a/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php +++ b/tests/php/Core/Platform/Infrastructure/Repository/FsReadUpdateRepositoryTest.php @@ -24,6 +24,7 @@ namespace Tests\Core\Platform\Infrastructure\Repository; use Core\Platform\Infrastructure\Repository\FsReadUpdateRepository; +use Core\Platform\Application\Repository\UpdateNotFoundException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; @@ -32,8 +33,8 @@ $this->finder = $this->createMock(Finder::class); }); -it('should not find updates if install directory does not exist', function () { - $repository = new FsReadUpdateRepository($this->filesystem, $this->finder); +it('should return an error when install directory does not exist', function () { + $repository = new FsReadUpdateRepository(sys_get_temp_dir(), $this->filesystem, $this->finder); $this->filesystem ->expects($this->once()) @@ -41,11 +42,13 @@ ->willReturn(false); $availableUpdates = $repository->findOrderedAvailableUpdates('22.04.0'); - expect($availableUpdates)->toEqual([]); -}); +})->throws( + UpdateNotFoundException::class, + UpdateNotFoundException::updatesNotFound()->getMessage(), +); it('should order found updates', function () { - $repository = new FsReadUpdateRepository($this->filesystem, $this->finder); + $repository = new FsReadUpdateRepository(sys_get_temp_dir(), $this->filesystem, $this->finder); $this->filesystem ->expects($this->once()) From 6bc3722667345900d4d4eb2140bf0d59544b228b Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 15 Jul 2022 15:07:15 +0200 Subject: [PATCH 5/8] enh(update): check platform requirements on updates API call (#11361) Refs: MON-12296 --- config/packages/Centreon.yaml | 21 +++ config/services.yaml | 2 + lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 15 ++ .../UseCase/UpdateVersions/UpdateVersions.php | 17 +++ .../Validator/RequirementException.php | 27 ++++ .../RequirementValidatorInterface.php | 33 +++++ .../RequirementValidatorsInterface.php | 33 +++++ .../UpdateVersionsController.php | 1 - .../Validator/RequirementValidators.php | 63 +++++++++ .../DatabaseRequirementException.php | 49 +++++++ .../DatabaseRequirementValidator.php | 128 ++++++++++++++++++ .../DatabaseRequirementValidatorInterface.php | 43 ++++++ .../MariaDbRequirementException.php | 44 ++++++ .../MariaDbRequirementValidator.php | 82 +++++++++++ .../PhpRequirementException.php | 56 ++++++++ .../PhpRequirementValidator.php | 108 +++++++++++++++ .../UpdateVersions/UpdateVersionsTest.php | 30 +++- tests/php/bootstrap.php | 3 +- 18 files changed, 751 insertions(+), 4 deletions(-) create mode 100644 src/Core/Platform/Application/Validator/RequirementException.php create mode 100644 src/Core/Platform/Application/Validator/RequirementValidatorInterface.php create mode 100644 src/Core/Platform/Application/Validator/RequirementValidatorsInterface.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidators/MariaDbRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidators/MariaDbRequirementValidator.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php create mode 100644 src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementValidator.php diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index c90074e86d6..7e19c9b5ea7 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -220,6 +220,23 @@ services: class: Core\Infrastructure\Platform\Repository\FileReadPlatformRepository arguments: ['%centreon_etc_path%', '%centreon_install_path%'] + Core\Platform\Application\Validator\RequirementValidatorsInterface: + class: Core\Platform\Infrastructure\Validator\RequirementValidators + arguments: + $requirementValidators: !tagged_iterator 'platform.requirement.validators' + + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidator: + arguments: + $dbRequirementValidators: !tagged_iterator 'platform.requirement.database.validators' + + Core\Platform\Infrastructure\Validator\RequirementValidators\PhpRequirementValidator: + arguments: + $requiredPhpVersion: '%required_php_version%' + + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidators\MariaDbRequirementValidator: + arguments: + $requiredMariaDbMinVersion: '%required_mariadb_min_version%' + Core\Platform\Application\Repository\ReadVersionRepositoryInterface: class: Core\Platform\Infrastructure\Repository\DbReadVersionRepository public: true @@ -254,6 +271,10 @@ services: tags: ['authentication.provider.responses'] Core\Security\Infrastructure\Api\FindProviderConfigurations\ProviderPresenter\ProviderPresenterInterface: tags: ['authentication.provider.presenters'] + Core\Platform\Application\Validator\RequirementValidatorInterface: + tags: ['platform.requirement.validators'] + Core\Platform\Infrastructure\Validator\RequirementValidators\DatabaseRequirementValidatorInterface: + tags: ['platform.requirement.database.validators'] Centreon\Domain\Monitoring\Interfaces\ResourceRepositoryInterface: factory: ['@Centreon\Infrastructure\Monitoring\Resource\ResourceRepositoryFactory', 'createResourceRepository'] diff --git a/config/services.yaml b/config/services.yaml index d1ca3a46121..566596ebc51 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -22,6 +22,8 @@ parameters: media_path: "img/media" redirect_default_page: "/monitoring/resources" session_expiration_delay: 120 + required_php_version: "%env(_CENTREON_PHP_VERSION_)%" + required_mariadb_min_version: "%env(_CENTREON_MARIA_DB_MIN_VERSION_)%" services: # Default configuration for services in *this* file diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index d3e1f674224..b96dea0e1fb 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16943,3 +16943,18 @@ msgstr "Une erreur s'est produite lors de l'application des actions postérieure msgid "Updates not found" msgstr "Les mises à jour n'ont pas été trouvées" + +msgid "PHP version %s required (%s installed)" +msgstr "La version %s de PHP est requise (%s installée)" + +msgid "PHP extension %s not loaded" +msgstr "L'extension %s de PHP n'est pas chargée" + +msgid "Error when getting database version" +msgstr "Erreur lors de la récupération de la version de la base de données" + +msgid "Cannot retrieve database version information" +msgstr "Les informations de version de la base de données n'ont pas pu être trouvées" + +msgid "MariaDB version %s required (%s installed)" +msgstr "La version %s de MariaDB est requise (%s installée)" diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php index f0127fc3511..038cb2ecba7 100644 --- a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersions.php @@ -23,6 +23,7 @@ namespace Core\Platform\Application\UseCase\UpdateVersions; use Centreon\Domain\Log\LoggerTrait; +use Core\Platform\Application\Validator\RequirementValidatorsInterface; use Core\Platform\Application\Repository\UpdateLockerRepositoryInterface; use Core\Platform\Application\Repository\ReadVersionRepositoryInterface; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; @@ -37,12 +38,14 @@ class UpdateVersions use LoggerTrait; /** + * @param RequirementValidatorsInterface $requirementValidators * @param UpdateLockerRepositoryInterface $updateLocker * @param ReadVersionRepositoryInterface $readVersionRepository * @param ReadUpdateRepositoryInterface $readUpdateRepository * @param WriteUpdateRepositoryInterface $writeUpdateRepository */ public function __construct( + private RequirementValidatorsInterface $requirementValidators, private UpdateLockerRepositoryInterface $updateLocker, private ReadVersionRepositoryInterface $readVersionRepository, private ReadUpdateRepositoryInterface $readUpdateRepository, @@ -59,6 +62,8 @@ public function __invoke( $this->info('Updating versions'); try { + $this->validateRequirementsOrFail(); + $this->lockUpdate(); $currentVersion = $this->getCurrentVersionOrFail(); @@ -93,6 +98,18 @@ public function __invoke( $presenter->setResponseStatus(new NoContentResponse()); } + /** + * Validate platform requirements or fail + * + * @throws \Exception + */ + private function validateRequirementsOrFail(): void + { + $this->info('Validating platform requirements'); + + $this->requirementValidators->validateRequirementsOrFail(); + } + /** * Lock update process */ diff --git a/src/Core/Platform/Application/Validator/RequirementException.php b/src/Core/Platform/Application/Validator/RequirementException.php new file mode 100644 index 00000000000..d4feca77392 --- /dev/null +++ b/src/Core/Platform/Application/Validator/RequirementException.php @@ -0,0 +1,27 @@ + $requirementValidators + * + * @throws \Exception + */ + public function __construct( + \Traversable $requirementValidators, + ) { + if (iterator_count($requirementValidators) === 0) { + throw new \Exception('Requirement validators not found'); + } + $this->requirementValidators = iterator_to_array($requirementValidators); + } + + /** + * @inheritDoc + */ + public function validateRequirementsOrFail(): void + { + foreach ($this->requirementValidators as $requirementValidator) { + $this->info('Validating platform requirement with ' . $requirementValidator::class); + $requirementValidator->validateRequirementOrFail(); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php new file mode 100644 index 00000000000..f478124932f --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php @@ -0,0 +1,49 @@ + $dbRequirementValidators + * + * @throws \Exception + */ + public function __construct( + DatabaseConnection $db, + \Traversable $dbRequirementValidators, + ) { + $this->db = $db; + + if (iterator_count($dbRequirementValidators) === 0) { + throw new \Exception('Database requirement validators not found'); + } + $this->dbRequirementValidators = iterator_to_array($dbRequirementValidators); + } + + /** + * {@inheritDoc} + * + * @throws DatabaseRequirementException + */ + public function validateRequirementOrFail(): void + { + $this->initDatabaseVersionInformation(); + + foreach ($this->dbRequirementValidators as $dbRequirementValidator) { + if ($dbRequirementValidator->isValidFor($this->versionComment)) { + $this->info( + 'Validating requirement by ' . $dbRequirementValidator::class, + [ + 'current_version' => $this->version, + ], + ); + $dbRequirementValidator->validateRequirementOrFail($this->version); + $this->info('Requirement validated by ' . $dbRequirementValidator::class); + } + } + } + + /** + * Get database version information + * + * @throws DatabaseRequirementException + */ + private function initDatabaseVersionInformation(): void + { + $this->info('Getting database version information'); + + try { + $statement = $this->db->query("SHOW VARIABLES WHERE Variable_name IN ('version', 'version_comment')"); + while ($statement !== false && is_array($row = $statement->fetch(\PDO::FETCH_ASSOC))) { + if ($row['Variable_name'] === "version") { + $this->info('Retrieved DBMS version: ' . $row['Value']); + $this->version = $row['Value']; + } elseif ($row['Variable_name'] === "version_comment") { + $this->info('Retrieved DBMS version comment: ' . $row['Value']); + $this->versionComment = $row['Value']; + } + } + } catch (\Throwable $e) { + $this->error( + 'Error when getting DBMS version from database', + [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ], + ); + throw DatabaseRequirementException::errorWhenGettingDatabaseVersion($e); + } + + if (empty($this->version) || empty($this->versionComment)) { + $this->info('Cannot retrieve database version information'); + throw DatabaseRequirementException::cannotRetrieveVersionInformation(); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php new file mode 100644 index 00000000000..ba44860931b --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidatorInterface.php @@ -0,0 +1,43 @@ +info( + 'Checking if version comment contains MariaDB string', + [ + 'version_comment' => $versionComment, + ], + ); + + return strpos($versionComment, "MariaDB") !== false; + } + + /** + * {@inheritDoc} + * + * @throws MariaDbRequirementException + */ + public function validateRequirementOrFail(string $version): void + { + $currentMariaDBMajorVersion = VersionHelper::regularizeDepthVersion($version, 1); + + $this->info( + 'Comparing current MariaDB version ' . $currentMariaDBMajorVersion + . ' to minimal required version ' . $this->requiredMariaDbMinVersion + ); + + if ( + VersionHelper::compare($currentMariaDBMajorVersion, $this->requiredMariaDbMinVersion, VersionHelper::LT) + ) { + $this->error('MariaDB requirement is not validated'); + + throw MariaDbRequirementException::badMariaDbVersion( + $this->requiredMariaDbMinVersion, + $currentMariaDBMajorVersion, + ); + } + } +} diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php new file mode 100644 index 00000000000..8086d3d86bc --- /dev/null +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/PhpRequirementException.php @@ -0,0 +1,56 @@ +validatePhpVersionOrFail(); + $this->validatePhpExtensionsOrFail(); + } + + /** + * Check installed php version + * + * @throws PhpRequirementException + */ + private function validatePhpVersionOrFail(): void + { + $currentPhpMajorVersion = VersionHelper::regularizeDepthVersion(PHP_VERSION, 1); + + $this->info( + 'Comparing current PHP version ' . $currentPhpMajorVersion + . ' to required version ' . $this->requiredPhpVersion + ); + if (! VersionHelper::compare($currentPhpMajorVersion, $this->requiredPhpVersion, VersionHelper::EQUAL)) { + throw PhpRequirementException::badPhpVersion($this->requiredPhpVersion, $currentPhpMajorVersion); + } + } + + /** + * Check if required php extensions are loaded + * + * @throws PhpRequirementException + */ + private function validatePhpExtensionsOrFail(): void + { + $this->info('Checking PHP extensions'); + foreach (self::EXTENSION_REQUIREMENTS as $extensionName) { + $this->validatePhpExtensionOrFail($extensionName); + } + } + + /** + * check if given php extension is loaded + * + * @param string $extensionName + * + * @throws PhpRequirementException + */ + private function validatePhpExtensionOrFail(string $extensionName): void + { + $this->info('Checking PHP extension ' . $extensionName); + if (! extension_loaded($extensionName)) { + $this->error('PHP extension ' . $extensionName . ' is not loaded'); + throw PhpRequirementException::phpExtensionNotLoaded($extensionName); + } + } +} diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php index 4b402bff1b4..26dbef00733 100644 --- a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -25,6 +25,8 @@ use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersions; use Core\Platform\Application\UseCase\UpdateVersions\UpdateVersionsPresenterInterface; +use Core\Platform\Application\Validator\RequirementException; +use Core\Platform\Application\Validator\RequirementValidatorsInterface; use Core\Platform\Application\Repository\UpdateLockerRepositoryInterface; use Core\Platform\Application\Repository\ReadVersionRepositoryInterface; use Core\Platform\Application\Repository\ReadUpdateRepositoryInterface; @@ -33,6 +35,7 @@ use Core\Application\Common\UseCase\NoContentResponse; beforeEach(function () { + $this->requirementValidators = $this->createMock(RequirementValidatorsInterface::class); $this->updateLockerRepository = $this->createMock(UpdateLockerRepositoryInterface::class); $this->readVersionRepository = $this->createMock(ReadVersionRepositoryInterface::class); $this->readUpdateRepository = $this->createMock(ReadUpdateRepositoryInterface::class); @@ -42,6 +45,7 @@ it('should stop update process when an other update is already started', function () { $updateVersions = new UpdateVersions( + $this->requirementValidators, $this->updateLockerRepository, $this->readVersionRepository, $this->readUpdateRepository, @@ -61,8 +65,31 @@ $updateVersions($this->presenter); }); -it('should present an error response if current version is not found', function () { +it('should present an error response if a requirement is not validated', function () { $updateVersions = new UpdateVersions( + $this->requirementValidators, + $this->updateLockerRepository, + $this->readVersionRepository, + $this->readUpdateRepository, + $this->writeUpdateRepository, + ); + + $this->requirementValidators + ->expects($this->once()) + ->method('validateRequirementsOrFail') + ->willThrowException(new RequirementException('Requirement is not validated')); + + $this->presenter + ->expects($this->once()) + ->method('setResponseStatus') + ->with(new ErrorResponse('Requirement is not validated')); + + $updateVersions($this->presenter); +}); + +it('should present an error response if current centreon version is not found', function () { + $updateVersions = new UpdateVersions( + $this->requirementValidators, $this->updateLockerRepository, $this->readVersionRepository, $this->readUpdateRepository, @@ -89,6 +116,7 @@ it('should run found updates', function () { $updateVersions = new UpdateVersions( + $this->requirementValidators, $this->updateLockerRepository, $this->readVersionRepository, $this->readUpdateRepository, diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php index 2ca3e800b03..d1d41179b9f 100644 --- a/tests/php/bootstrap.php +++ b/tests/php/bootstrap.php @@ -24,8 +24,7 @@ } $mockedPreRequisiteConstants = [ - '_CENTREON_PHP_MIN_VERSION_' => '8.0', - '_CENTREON_PHP_MAX_VERSION_' => '8.0', + '_CENTREON_PHP_VERSION_' => '8.0', '_CENTREON_MARIA_DB_MIN_VERSION_' => '10.5', ]; foreach ($mockedPreRequisiteConstants as $mockedPreRequisiteConstant => $value) { From 109774d607a2a24a7f567848a62522deba85629c Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Mon, 18 Jul 2022 10:55:49 +0200 Subject: [PATCH 6/8] fix wording --- lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 18 +++++++++--------- .../Repository/UpdateLockerException.php | 2 +- .../UpdateVersions/UpdateVersionsException.php | 8 ++++---- .../DatabaseRequirementException.php | 4 ++-- .../DatabaseRequirementValidator.php | 2 +- .../UpdateVersions/UpdateVersionsTest.php | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index b96dea0e1fb..49142b5d656 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16920,19 +16920,19 @@ msgstr "Attention, taille maximale dépassée pour le champ '%s' (max: %d), il s msgid "Update already in progress" msgstr "Une mise à jour est déjà en cours" -msgid "An error occurred when retrieving current version" +msgid "An error occurred when retrieving the current version" msgstr "Une erreur s'est produite lors de la récupération de la version actuelle" -msgid "Cannot retrieve current version" -msgstr "La version actuelle n'a pas pu être trouvée" +msgid "Cannot retrieve the current version" +msgstr "La version actuelle n'a pas pu être récupérée" -msgid "An error occurred when getting available updates" +msgid "An error occurred when retrieving available updates" msgstr "Une erreur s'est produite lors de la récupération des mises à jour disponibles" -msgid "An error occurred when applying update %s (%s)" +msgid "An error occurred when applying the update %s (%s)" msgstr "Une erreur s'est produite lors de l'application de la mise à jour %s (%s)" -msgid "Error while locking update process" +msgid "Error while locking the update process" msgstr "Erreur lors du verrouillage du processus de mise à jour" msgid "Error while unlocking update process" @@ -16950,11 +16950,11 @@ msgstr "La version %s de PHP est requise (%s installée)" msgid "PHP extension %s not loaded" msgstr "L'extension %s de PHP n'est pas chargée" -msgid "Error when getting database version" +msgid "Error when retrieving the database version" msgstr "Erreur lors de la récupération de la version de la base de données" -msgid "Cannot retrieve database version information" -msgstr "Les informations de version de la base de données n'ont pas pu être trouvées" +msgid "Cannot retrieve the database version information" +msgstr "Les informations de version de la base de données n'ont pas pu être récupérées" msgid "MariaDB version %s required (%s installed)" msgstr "La version %s de MariaDB est requise (%s installée)" diff --git a/src/Core/Platform/Application/Repository/UpdateLockerException.php b/src/Core/Platform/Application/Repository/UpdateLockerException.php index 57fbfff71e3..835decfe21b 100644 --- a/src/Core/Platform/Application/Repository/UpdateLockerException.php +++ b/src/Core/Platform/Application/Repository/UpdateLockerException.php @@ -31,7 +31,7 @@ class UpdateLockerException extends RepositoryException */ public static function errorWhileLockingUpdate(\Throwable $e): self { - return new self(_('Error while locking update process'), 0, $e); + return new self(_('Error while locking the update process'), 0, $e); } /** diff --git a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php index 58045eed72c..dbfaec97ba3 100644 --- a/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php +++ b/src/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsException.php @@ -38,7 +38,7 @@ public static function updateAlreadyInProgress(): self */ public static function errorWhenRetrievingCurrentVersion(\Throwable $e): self { - return new self(_('An error occurred when retrieving current version'), 0, $e); + return new self(_('An error occurred when retrieving the current version'), 0, $e); } /** @@ -46,7 +46,7 @@ public static function errorWhenRetrievingCurrentVersion(\Throwable $e): self */ public static function cannotRetrieveCurrentVersion(): self { - return new self(_('Cannot retrieve current version')); + return new self(_('Cannot retrieve the current version')); } /** @@ -55,7 +55,7 @@ public static function cannotRetrieveCurrentVersion(): self */ public static function errorWhenRetrievingAvailableUpdates(\Throwable $e): self { - return new self(_('An error occurred when getting available updates'), 0, $e); + return new self(_('An error occurred when retrieving available updates'), 0, $e); } /** @@ -70,7 +70,7 @@ public static function errorWhenApplyingUpdate( \Throwable $e ): self { return new self( - sprintf(_('An error occurred when applying update %s (%s)'), $version, $technicalMessage), + sprintf(_('An error occurred when applying the update %s (%s)'), $version, $technicalMessage), 0, $e ); diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php index f478124932f..f9517d8216a 100644 --- a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementException.php @@ -33,7 +33,7 @@ class DatabaseRequirementException extends RequirementException public static function errorWhenGettingDatabaseVersion(\Throwable $e): self { return new self( - _('Error when getting database version'), + _('Error when retrieving the database version'), 0, $e, ); @@ -44,6 +44,6 @@ public static function errorWhenGettingDatabaseVersion(\Throwable $e): self */ public static function cannotRetrieveVersionInformation(): self { - return new self(_('Cannot retrieve database version information')); + return new self(_('Cannot retrieve the database version information')); } } diff --git a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php index e2cd200e119..619d338eddf 100644 --- a/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php +++ b/src/Core/Platform/Infrastructure/Validator/RequirementValidators/DatabaseRequirementValidator.php @@ -121,7 +121,7 @@ private function initDatabaseVersionInformation(): void } if (empty($this->version) || empty($this->versionComment)) { - $this->info('Cannot retrieve database version information'); + $this->info('Cannot retrieve the database version information'); throw DatabaseRequirementException::cannotRetrieveVersionInformation(); } } diff --git a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php index 26dbef00733..6f96a95531a 100644 --- a/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php +++ b/tests/php/Core/Platform/Application/UseCase/UpdateVersions/UpdateVersionsTest.php @@ -109,7 +109,7 @@ $this->presenter ->expects($this->once()) ->method('setResponseStatus') - ->with(new ErrorResponse('Cannot retrieve current version')); + ->with(new ErrorResponse('Cannot retrieve the current version')); $updateVersions($this->presenter); }); From 5f4adb98d8f5a3cc26130e97944b86b1ed35b5da Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Mon, 18 Jul 2022 10:56:43 +0200 Subject: [PATCH 7/8] fix wording --- lang/fr_FR.UTF-8/LC_MESSAGES/messages.po | 2 +- .../Platform/Application/Repository/UpdateLockerException.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po index 49142b5d656..1445913e5f2 100644 --- a/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po +++ b/lang/fr_FR.UTF-8/LC_MESSAGES/messages.po @@ -16935,7 +16935,7 @@ msgstr "Une erreur s'est produite lors de l'application de la mise à jour %s (% msgid "Error while locking the update process" msgstr "Erreur lors du verrouillage du processus de mise à jour" -msgid "Error while unlocking update process" +msgid "Error while unlocking the update process" msgstr "Erreur lors du déverrouillage du processus de mise à jour" msgid "An error occurred when applying post update actions" diff --git a/src/Core/Platform/Application/Repository/UpdateLockerException.php b/src/Core/Platform/Application/Repository/UpdateLockerException.php index 835decfe21b..9f5c3e892ae 100644 --- a/src/Core/Platform/Application/Repository/UpdateLockerException.php +++ b/src/Core/Platform/Application/Repository/UpdateLockerException.php @@ -39,6 +39,6 @@ public static function errorWhileLockingUpdate(\Throwable $e): self */ public static function errorWhileUnlockingUpdate(\Throwable $e): self { - return new self(_('Error while unlocking update process'), 0, $e); + return new self(_('Error while unlocking the update process'), 0, $e); } } From 651311e5633022929df142a3167d9426b9f40499 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Tue, 19 Jul 2022 10:57:14 +0200 Subject: [PATCH 8/8] enh(packaging): rebuild symfony cache after centreon install/upgrade (#11323) Refs: MON-11323 --- ci/debian/centreon-web.postinst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ci/debian/centreon-web.postinst b/ci/debian/centreon-web.postinst index 8d909ce272a..f6b23fe4e71 100644 --- a/ci/debian/centreon-web.postinst +++ b/ci/debian/centreon-web.postinst @@ -54,4 +54,10 @@ if [ "$1" = "configure" ] ; then fi fi + +# rebuild symfony cache on upgrade +if [ -n "$2" ]; then + su - www-data -s /bin/bash -c "/usr/share/centreon/bin/console cache:clear --no-warmup" +fi + exit 0