From b3e2a987db43f8fe391f6b6494c10c7d32637784 Mon Sep 17 00:00:00 2001 From: Morgan Pichat Date: Tue, 12 Nov 2024 15:00:50 +0100 Subject: [PATCH 1/2] Improve commands error handler --- classes/Commands/AbstractBackupCommand.php | 86 ++++++++++++++++++++++ classes/Commands/ListBackupCommand.php | 2 +- classes/Commands/RestoreCommand.php | 4 +- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 classes/Commands/AbstractBackupCommand.php diff --git a/classes/Commands/AbstractBackupCommand.php b/classes/Commands/AbstractBackupCommand.php new file mode 100644 index 000000000..b3986a73d --- /dev/null +++ b/classes/Commands/AbstractBackupCommand.php @@ -0,0 +1,86 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ + +namespace PrestaShop\Module\AutoUpgrade\Commands; + +use DateTime; +use Exception; +use PrestaShop\Module\AutoUpgrade\Backup\BackupFinder; +use PrestaShop\Module\AutoUpgrade\Exceptions\BackupException; +use PrestaShop\Module\AutoUpgrade\UpgradeContainer; + +abstract class AbstractBackupCommand extends AbstractCommand +{ + /** + * @throws Exception + * + * @return string[] + */ + protected function getBackups(): array + { + $backupPath = $this->upgradeContainer->getProperty(UpgradeContainer::BACKUP_PATH); + + return (new BackupFinder($backupPath))->getAvailableBackups(); + } + + /** + * @param string $backupName + * + * @return array{timestamp: int, datetime: string, version:string, filename: string} + * + * @throws BackupException + */ + protected function parseBackupMetadata(string $backupName): array + { + $pattern = '/V(\d+(\.\d+){1,3})_([0-9]{8})-([0-9]{6})/'; + if (preg_match($pattern, $backupName, $matches)) { + $version = $matches[1]; + $datePart = $matches[3]; + $timePart = $matches[4]; + + $dateTime = DateTime::createFromFormat('Ymd His', $datePart . ' ' . $timePart); + $timestamp = $dateTime->getTimestamp(); + + return + [ + 'timestamp' => $timestamp, + 'datetime' => $this->getFormattedDatetime($timestamp), + 'version' => $version, + 'filename' => $backupName, + ]; + } + + throw new BackupException('An error occurred while formatting the backup name.'); + } + + private function getFormattedDatetime(int $timestamp): string + { + setlocale(LC_TIME, ''); + + return strftime('%x %X', $timestamp); + } +} diff --git a/classes/Commands/ListBackupCommand.php b/classes/Commands/ListBackupCommand.php index a7c3d3456..7513206bf 100644 --- a/classes/Commands/ListBackupCommand.php +++ b/classes/Commands/ListBackupCommand.php @@ -37,7 +37,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -class ListBackupCommand extends AbstractCommand +class ListBackupCommand extends AbstractBackupCommand { /** @var string */ protected static $defaultName = 'backup:list'; diff --git a/classes/Commands/RestoreCommand.php b/classes/Commands/RestoreCommand.php index 8c6b6547e..c4cbd4678 100644 --- a/classes/Commands/RestoreCommand.php +++ b/classes/Commands/RestoreCommand.php @@ -28,9 +28,9 @@ namespace PrestaShop\Module\AutoUpgrade\Commands; use Exception; -use InvalidArgumentException; use PrestaShop\Module\AutoUpgrade\Backup\BackupFinder; use PrestaShop\Module\AutoUpgrade\Exceptions\BackupException; +use PrestaShop\Module\AutoUpgrade\Task\ExitCode; use PrestaShop\Module\AutoUpgrade\Task\Runner\AllRestoreTasks; use PrestaShop\Module\AutoUpgrade\UpgradeContainer; use Symfony\Component\Console\Input\InputArgument; @@ -39,7 +39,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; -class RestoreCommand extends AbstractCommand +class RestoreCommand extends AbstractBackupCommand { /** * @var string From 051ac3fed7e5bc336edda6409d88c3535117216a Mon Sep 17 00:00:00 2001 From: Morgan Pichat Date: Thu, 14 Nov 2024 10:28:25 +0100 Subject: [PATCH 2/2] Create delete backup command --- bin/console | 2 + classes/Backup/BackupManager.php | 2 +- classes/Commands/AbstractBackupCommand.php | 96 ++++++++++++------- classes/Commands/AbstractCommand.php | 2 +- classes/Commands/CheckRequirementsCommand.php | 4 +- classes/Commands/CreateBackupCommand.php | 2 +- classes/Commands/DeleteBackupCommand.php | 90 +++++++++++++++++ classes/Commands/ListBackupCommand.php | 18 ++-- classes/Commands/RestoreCommand.php | 61 ++---------- classes/Commands/UpdateCommand.php | 2 +- classes/Task/Miscellaneous/UpdateConfig.php | 2 +- 11 files changed, 174 insertions(+), 107 deletions(-) create mode 100644 classes/Commands/DeleteBackupCommand.php diff --git a/bin/console b/bin/console index 2902d7e2e..3f4cf5fa5 100755 --- a/bin/console +++ b/bin/console @@ -28,6 +28,7 @@ use PrestaShop\Module\AutoUpgrade\Commands\CheckRequirementsCommand; use PrestaShop\Module\AutoUpgrade\Commands\CreateBackupCommand; +use PrestaShop\Module\AutoUpgrade\Commands\DeleteBackupCommand; use PrestaShop\Module\AutoUpgrade\Commands\ListBackupCommand; use PrestaShop\Module\AutoUpgrade\Commands\RestoreCommand; use PrestaShop\Module\AutoUpgrade\Commands\UpdateCommand; @@ -52,6 +53,7 @@ $application->add(new RestoreCommand()); $application->add(new CheckRequirementsCommand()); $application->add(new CreateBackupCommand()); $application->add(new ListBackupCommand()); +$application->add(new DeleteBackupCommand()); $input = new ArgvInput(); $output = new ConsoleOutput(); diff --git a/classes/Backup/BackupManager.php b/classes/Backup/BackupManager.php index 316bdf942..5265e7591 100644 --- a/classes/Backup/BackupManager.php +++ b/classes/Backup/BackupManager.php @@ -53,7 +53,7 @@ public function deleteBackup(string $backupName): void $filesystem = new Filesystem(); $filesystem->remove([ - $this->backupFinder->getBackupPath() . DIRECTORY_SEPARATOR . BackupFinder::BACKUP_ZIP_NAME_PREFIX . $backupName, + $this->backupFinder->getBackupPath() . DIRECTORY_SEPARATOR . BackupFinder::BACKUP_ZIP_NAME_PREFIX . $backupName . '.zip', $this->backupFinder->getBackupPath() . DIRECTORY_SEPARATOR . $backupName, ]); diff --git a/classes/Commands/AbstractBackupCommand.php b/classes/Commands/AbstractBackupCommand.php index b3986a73d..13798d0de 100644 --- a/classes/Commands/AbstractBackupCommand.php +++ b/classes/Commands/AbstractBackupCommand.php @@ -30,57 +30,83 @@ use DateTime; use Exception; use PrestaShop\Module\AutoUpgrade\Backup\BackupFinder; +use PrestaShop\Module\AutoUpgrade\Backup\BackupManager; use PrestaShop\Module\AutoUpgrade\Exceptions\BackupException; use PrestaShop\Module\AutoUpgrade\UpgradeContainer; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; abstract class AbstractBackupCommand extends AbstractCommand { + /** @var BackupFinder */ + protected $backupFinder; + + /** @var BackupManager */ + protected $backupManager; + + protected function setupEnvironment(InputInterface $input, OutputInterface $output): void + { + parent::setupEnvironment($input, $output); + $this->backupFinder = new BackupFinder($this->upgradeContainer->getProperty(UpgradeContainer::BACKUP_PATH)); + $this->backupManager = new BackupManager($this->backupFinder); + } + /** * @throws Exception - * - * @return string[] */ - protected function getBackups(): array + protected function selectBackupInteractive(InputInterface $input, OutputInterface $output): ?string { - $backupPath = $this->upgradeContainer->getProperty(UpgradeContainer::BACKUP_PATH); + $backups = $this->backupFinder->getAvailableBackups(); + + if (empty($backups)) { + $this->logger->info('No store backup files found in your dedicated directory'); + + return null; + } + + $formattedBackups = array_map(function ($backupName) { + return $this->backupFinder->parseBackupMetadata($backupName); + }, $backups); + + $this->backupFinder->sortBackupsByNewest($formattedBackups); + + $rows = array_map(function ($backup) { + return $this->formatBackupRow($backup); + }, $formattedBackups); + + $exit = 'Exit the process'; + $rows[] = $exit; - return (new BackupFinder($backupPath))->getAvailableBackups(); + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select your backup:', + $rows + ); + + $answer = $helper->ask($input, $output, $question); + + if ($answer === $exit) { + return null; + } + + $key = array_search($answer, $rows); + if ($key === false) { + throw new BackupException('Invalid backup selection.'); + } + + return $formattedBackups[$key]['filename']; } /** - * @param string $backupName + * Formats a backup row for display in the selection prompt. * - * @return array{timestamp: int, datetime: string, version:string, filename: string} + * @param array{datetime: string, version:string, filename: string} $backups * - * @throws BackupException + * @return string */ - protected function parseBackupMetadata(string $backupName): array + private function formatBackupRow(array $backups): string { - $pattern = '/V(\d+(\.\d+){1,3})_([0-9]{8})-([0-9]{6})/'; - if (preg_match($pattern, $backupName, $matches)) { - $version = $matches[1]; - $datePart = $matches[3]; - $timePart = $matches[4]; - - $dateTime = DateTime::createFromFormat('Ymd His', $datePart . ' ' . $timePart); - $timestamp = $dateTime->getTimestamp(); - - return - [ - 'timestamp' => $timestamp, - 'datetime' => $this->getFormattedDatetime($timestamp), - 'version' => $version, - 'filename' => $backupName, - ]; - } - - throw new BackupException('An error occurred while formatting the backup name.'); - } - - private function getFormattedDatetime(int $timestamp): string - { - setlocale(LC_TIME, ''); - - return strftime('%x %X', $timestamp); + return sprintf('Date: %s, Version: %s, File name: %s', $backups['datetime'], $backups['version'], $backups['filename']); } } diff --git a/classes/Commands/AbstractCommand.php b/classes/Commands/AbstractCommand.php index b90c4cd41..3bae3145c 100644 --- a/classes/Commands/AbstractCommand.php +++ b/classes/Commands/AbstractCommand.php @@ -52,7 +52,7 @@ abstract class AbstractCommand extends Command /** * @throws Exception */ - protected function setupContainer(InputInterface $input, OutputInterface $output): void + protected function setupEnvironment(InputInterface $input, OutputInterface $output): void { $this->logger = new CliLogger($output); if ($output->isQuiet()) { diff --git a/classes/Commands/CheckRequirementsCommand.php b/classes/Commands/CheckRequirementsCommand.php index 364d286a0..717ccac26 100644 --- a/classes/Commands/CheckRequirementsCommand.php +++ b/classes/Commands/CheckRequirementsCommand.php @@ -31,6 +31,7 @@ use PrestaShop\Module\AutoUpgrade\Services\DistributionApiService; use PrestaShop\Module\AutoUpgrade\Services\PhpVersionResolverService; use PrestaShop\Module\AutoUpgrade\Task\ExitCode; +use PrestaShop\Module\AutoUpgrade\UpgradeContainer; use PrestaShop\Module\AutoUpgrade\UpgradeSelfCheck; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -64,7 +65,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): ?int { try { - $this->setupContainer($input, $output); + $this->setupEnvironment($input, $output); $this->output = $output; $configPath = $input->getOption('config-file-path'); @@ -77,6 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int $this->upgradeContainer->initPrestaShopAutoloader(); $this->upgradeContainer->initPrestaShopCore(); + $this->upgradeContainer->getState()->initDefault($this->upgradeContainer->getProperty(UpgradeContainer::PS_VERSION), $this->upgradeContainer->getUpgrader()->getDestinationVersion()); $distributionApiService = new DistributionApiService(); $phpVersionResolverService = new PhpVersionResolverService( diff --git a/classes/Commands/CreateBackupCommand.php b/classes/Commands/CreateBackupCommand.php index 3e9e8c74e..760e6dca4 100644 --- a/classes/Commands/CreateBackupCommand.php +++ b/classes/Commands/CreateBackupCommand.php @@ -54,7 +54,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): ?int { try { - $this->setupContainer($input, $output); + $this->setupEnvironment($input, $output); $controller = new AllBackupTasks($this->upgradeContainer); $controller->init(); diff --git a/classes/Commands/DeleteBackupCommand.php b/classes/Commands/DeleteBackupCommand.php new file mode 100644 index 000000000..ad13291e2 --- /dev/null +++ b/classes/Commands/DeleteBackupCommand.php @@ -0,0 +1,90 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ + +namespace PrestaShop\Module\AutoUpgrade\Commands; + +use Exception; +use InvalidArgumentException; +use PrestaShop\Module\AutoUpgrade\Task\ExitCode; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class DeleteBackupCommand extends AbstractBackupCommand +{ + /** + * @var string + */ + protected static $defaultName = 'backup:delete'; + + protected function configure(): void + { + $this + ->setDescription('Delete a store backup file.') + ->setHelp( + 'This command allows you to delete a store backup file.' + ) + ->addArgument('admin-dir', InputArgument::REQUIRED, 'The admin directory name.') + ->addOption('backup', null, InputOption::VALUE_REQUIRED, 'Specify the backup name to delete. The allowed values can be found with backup:list command)'); + } + + /** + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output): ?int + { + try { + $this->setupEnvironment($input, $output); + + $backup = $input->getOption('backup'); + $exitCode = ExitCode::SUCCESS; + + if (!$backup) { + if (!$input->isInteractive()) { + throw new InvalidArgumentException("The '--backup' option is required."); + } + + $backup = $this->selectBackupInteractive($input, $output); + + if (!$backup) { + return $exitCode; + } + } + + $this->backupManager->deleteBackup($backup); + $this->logger->info('The backup file has been successfully deleted'); + + $this->logger->debug('Process completed with exit code: ' . $exitCode); + + return $exitCode; + } catch (Exception $e) { + $this->logger->error('An error occurred during the delete backup process'); + throw $e; + } + } +} diff --git a/classes/Commands/ListBackupCommand.php b/classes/Commands/ListBackupCommand.php index 7513206bf..7a78d8ff7 100644 --- a/classes/Commands/ListBackupCommand.php +++ b/classes/Commands/ListBackupCommand.php @@ -28,10 +28,8 @@ namespace PrestaShop\Module\AutoUpgrade\Commands; use Exception; -use PrestaShop\Module\AutoUpgrade\Backup\BackupFinder; use PrestaShop\Module\AutoUpgrade\Exceptions\BackupException; use PrestaShop\Module\AutoUpgrade\Task\ExitCode; -use PrestaShop\Module\AutoUpgrade\UpgradeContainer; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -56,11 +54,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): ?int { try { - $this->setupContainer($input, $output); + $this->setupEnvironment($input, $output); - $backupPath = $this->upgradeContainer->getProperty(UpgradeContainer::BACKUP_PATH); - $backupFinder = new BackupFinder($backupPath); - $backups = $backupFinder->getAvailableBackups(); + $backups = $this->backupFinder->getAvailableBackups(); if (empty($backups)) { $this->logger->info('No store backup files found in your dedicated directory'); @@ -68,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int return ExitCode::SUCCESS; } - $rows = $this->getRows($backupFinder, $backups); + $rows = $this->getRows($backups); $table = new Table($output); $table ->setHeaders(['Date', 'Version', 'File name']) @@ -90,13 +86,13 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int * * @throws BackupException */ - private function getRows(BackupFinder $backupFinder, array $backups): array + private function getRows(array $backups): array { - $rows = array_map(function ($backupName) use ($backupFinder) { - return $backupFinder->parseBackupMetadata($backupName); + $rows = array_map(function ($backupName) { + return $this->backupFinder->parseBackupMetadata($backupName); }, $backups); - $backupFinder->sortBackupsByNewest($rows); + $this->backupFinder->sortBackupsByNewest($rows); foreach ($rows as &$row) { unset($row['timestamp']); diff --git a/classes/Commands/RestoreCommand.php b/classes/Commands/RestoreCommand.php index c4cbd4678..40a543f0d 100644 --- a/classes/Commands/RestoreCommand.php +++ b/classes/Commands/RestoreCommand.php @@ -28,16 +28,13 @@ namespace PrestaShop\Module\AutoUpgrade\Commands; use Exception; -use PrestaShop\Module\AutoUpgrade\Backup\BackupFinder; -use PrestaShop\Module\AutoUpgrade\Exceptions\BackupException; +use InvalidArgumentException; use PrestaShop\Module\AutoUpgrade\Task\ExitCode; use PrestaShop\Module\AutoUpgrade\Task\Runner\AllRestoreTasks; -use PrestaShop\Module\AutoUpgrade\UpgradeContainer; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; class RestoreCommand extends AbstractBackupCommand { @@ -64,7 +61,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): ?int { try { - $this->setupContainer($input, $output); + $this->setupEnvironment($input, $output); $backup = $input->getOption('backup'); @@ -74,6 +71,10 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int } $backup = $this->selectBackupInteractive($input, $output); + + if (!$backup) { + return ExitCode::SUCCESS; + } } $controller = new AllRestoreTasks($this->upgradeContainer); @@ -90,54 +91,4 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int throw $e; } } - - /** - * @throws Exception - */ - private function selectBackupInteractive(InputInterface $input, OutputInterface $output): string - { - $backupPath = $this->upgradeContainer->getProperty(UpgradeContainer::BACKUP_PATH); - $backupFinder = new BackupFinder($backupPath); - $backups = $backupFinder->getAvailableBackups(); - - if (empty($backups)) { - throw new BackupException('No store backup files found in your dedicated directory'); - } - - $formattedBackups = array_map(function ($backupName) use ($backupFinder) { - return $backupFinder->parseBackupMetadata($backupName); - }, $backups); - - $backupFinder->sortBackupsByNewest($formattedBackups); - - $rows = array_map(function ($backup) { - return $this->formatBackupRow($backup); - }, $formattedBackups); - - $helper = $this->getHelper('question'); - $question = new ChoiceQuestion( - 'Please select your backup:', - $rows - ); - - $answer = $helper->ask($input, $output, $question); - $key = array_search($answer, $rows); - if ($key === false) { - throw new BackupException('Invalid backup selection.'); - } - - return $formattedBackups[$key]['filename']; - } - - /** - * Formats a backup row for display in the selection prompt. - * - * @param array{datetime: string, version:string, filename: string} $backups - * - * @return string - */ - private function formatBackupRow(array $backups): string - { - return sprintf('Date: %s, Version: %s, File name: %s', $backups['datetime'], $backups['version'], $backups['filename']); - } } diff --git a/classes/Commands/UpdateCommand.php b/classes/Commands/UpdateCommand.php index a8defb1fe..32fb32582 100644 --- a/classes/Commands/UpdateCommand.php +++ b/classes/Commands/UpdateCommand.php @@ -75,7 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int } try { - $this->setupContainer($input, $output); + $this->setupEnvironment($input, $output); // in the case of commands containing the update status, it is not necessary to update the configuration // also we do not want to repeat the update of the config in the recursive commands diff --git a/classes/Task/Miscellaneous/UpdateConfig.php b/classes/Task/Miscellaneous/UpdateConfig.php index 304cc2bfa..89749498e 100644 --- a/classes/Task/Miscellaneous/UpdateConfig.php +++ b/classes/Task/Miscellaneous/UpdateConfig.php @@ -165,7 +165,7 @@ private function writeConfig(array $config): bool $classConfig = $this->container->getUpgradeConfiguration(); $classConfig->merge($config); - $this->logger->info($this->translator->trans('Configuration successfully updated.') . ' ' . $this->translator->trans('This page will now be reloaded and the module will check if a new version is available.') . ''); + $this->logger->info($this->translator->trans('Configuration successfully updated.')); $this->container->getLogger()->debug('Configuration update: ' . json_encode($classConfig->toArray(), JSON_PRETTY_PRINT));