Skip to content

Commit be487cc

Browse files
committed
improve restoring process with test & better confirmation step
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent 3033160 commit be487cc

File tree

5 files changed

+233
-59
lines changed

5 files changed

+233
-59
lines changed

lib/Command/PointRestore.php

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use OCA\Backup\Exceptions\RestoringPointNotInitiatedException;
4747
use OCA\Backup\Exceptions\SqlDumpException;
4848
use OCA\Backup\Exceptions\SqlImportException;
49+
use OCA\Backup\Exceptions\SqlParamsException;
4950
use OCA\Backup\ISqlDump;
5051
use OCA\Backup\Model\ChangedFile;
5152
use OCA\Backup\Model\RestoringData;
@@ -147,6 +148,8 @@ protected function configure() {
147148
->setDescription('Restore a restoring point')
148149
->addArgument('pointId', InputArgument::REQUIRED, 'Id of the restoring point')
149150
->addOption('force', '', InputOption::VALUE_NONE, 'Force the restoring process')
151+
->addOption('do-not-ask-data', '', InputOption::VALUE_NONE, 'Do not ask for path on data')
152+
->addOption('do-not-ask-sql', '', InputOption::VALUE_NONE, 'Do not ask for params on sqldump')
150153
->addOption('file', '', InputOption::VALUE_REQUIRED, 'restore only a specific file')
151154
->addOption('chunk', '', InputOption::VALUE_REQUIRED, 'location of the file')
152155
->addOption('data', '', InputOption::VALUE_REQUIRED, 'location of the file');
@@ -261,8 +264,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
261264
$output->writeln('> <info>maintenance mode</info> disabled');
262265
$this->restoreService->finalizeFullRestore();
263266

264-
// $this->configService->maintenanceMode(false);
265-
266267
$this->activityService->newActivity(
267268
ActivityService::RESTORE,
268269
[
@@ -291,7 +292,7 @@ public function restorePointComplete(RestoringPoint $point): void {
291292
$this->output->writeln(' > Found data pack: <info>' . $data->getName() . '</info>');
292293

293294
if ($data->getType() === RestoringData::INTERNAL_DATA) {
294-
$this->output->writeln(' * ignoring');
295+
$this->output->writeln(' * ignoring');
295296
continue;
296297
}
297298

@@ -312,7 +313,13 @@ public function restorePointComplete(RestoringPoint $point): void {
312313
* @throws RestoringPointNotInitiatedException
313314
*/
314315
private function restorePointData(RestoringPoint $point, RestoringData $data): void {
315-
$root = $this->requestDataRoot($data);
316+
try {
317+
$root = $this->requestDataRoot($data);
318+
} catch (RestoringDataNotFoundException $e) {
319+
$this->output->writeln(' * ignoring data pack');
320+
321+
return;
322+
}
316323

317324
$this->output->writeln(' > extracting data to <info>' . $root . '</info>');
318325
foreach ($data->getChunks() as $chunk) {
@@ -345,16 +352,29 @@ private function restorePointData(RestoringPoint $point, RestoringData $data): v
345352
* @throws SqlDumpException
346353
*/
347354
private function restorePointSqlDump(RestoringPoint $point, RestoringData $data): void {
348-
$sqlParams = $this->requestSqlParams();
355+
while (true) {
356+
try {
357+
$sqlParams = $this->requestSqlParams();
358+
} catch (RestoringDataNotFoundException $e) {
359+
$this->output->writeln(' * ignoring sqldump');
349360

350-
$this->output->write(
351-
' > importing sqldump in <info>' . $this->displaySqlParams($sqlParams, true) . '</info>: '
352-
);
353-
try {
354-
$this->importSqlDump($point, $data, $sqlParams);
355-
$this->output->writeln('<info>ok</info>');
356-
} catch (SqlImportException $e) {
357-
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
361+
return;
362+
}
363+
364+
$this->output->write(
365+
' > importing sqldump in <info>' . $this->displaySqlParams($sqlParams, true) . '</info>: '
366+
);
367+
try {
368+
$this->importSqlDump($point, $data, $sqlParams);
369+
$this->output->writeln('<info>ok</info>');
370+
} catch (SqlParamsException $e) {
371+
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
372+
continue;
373+
} catch (SqlImportException $e) {
374+
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
375+
}
376+
377+
break;
358378
}
359379

360380
$data->setRestoredRoot(json_encode($sqlParams));
@@ -365,18 +385,37 @@ private function restorePointSqlDump(RestoringPoint $point, RestoringData $data)
365385
* @param RestoringData $data
366386
*
367387
* @return string
388+
* @throws RestoringDataNotFoundException
368389
*/
369390
private function requestDataRoot(RestoringData $data): string {
370391
$root = $data->getAbsolutePath();
392+
if ($this->input->getOption('do-not-ask-data')) {
393+
return $root;
394+
}
395+
371396
while (true) {
372397
$this->output->writeln(' > will be extracted in <info>' . $root . '</info>');
373398
$helper = $this->getHelper('question');
374399
$question = new Question(
375-
' - <comment>enter a new absolute path</comment> or press \'<info>enter</info>\' to use this location: ',
376-
$root
400+
' - <comment>enter a new absolute path</comment>, or type <info>yes</info> to confirm '
401+
. 'this location or <info>no</info> to ignore this part of the backup: ',
402+
''
377403
);
378-
$question->setAutocompleterValues([$data->getAbsolutePath()]);
404+
$question->setAutocompleterValues([$data->getAbsolutePath(), 'yes', 'no']);
379405
$newRoot = trim($helper->ask($this->input, $this->output, $question));
406+
407+
if ($newRoot === '') {
408+
continue;
409+
}
410+
411+
if ($newRoot === 'yes') {
412+
break;
413+
}
414+
415+
if ($newRoot === 'no') {
416+
throw new RestoringDataNotFoundException('ignoring');
417+
}
418+
380419
$newRoot = rtrim($newRoot, '/') . '/';
381420
if ($newRoot === $root) {
382421
break;
@@ -391,38 +430,51 @@ private function requestDataRoot(RestoringData $data): string {
391430

392431
/**
393432
* @return array
433+
* @throws RestoringDataNotFoundException
394434
*/
395435
private function requestSqlParams(): array {
396436
$sqlParams = $this->pointService->getSqlParams();
437+
if ($this->input->getOption('do-not-ask-sql')) {
438+
return $sqlParams;
439+
}
397440

398441
while (true) {
399442
$this->output->writeln(' > will be imported in ' . $this->displaySqlParams($sqlParams, true));
400443

401444
$helper = $this->getHelper('question');
402-
$question = new ConfirmationQuestion(
403-
'<comment> - Do you want to import the dump in another SQL server or database ?</comment> (y/N) ',
404-
false,
405-
'/^(y|Y)/i'
445+
$question = new Question(
446+
'<comment> - Do you want to import the dump in the current database, or cancel the import ?</comment> (yes/No/cancel) ',
447+
'no',
406448
);
449+
$question->setAutocompleterValues(['cancel', 'yes', 'no']);
407450

408-
if (!$helper->ask($this->input, $this->output, $question)) {
409-
return $sqlParams;
451+
switch (strtolower($helper->ask($this->input, $this->output, $question))) {
452+
case 'yes':
453+
return $sqlParams;
454+
case 'cancel':
455+
throw new RestoringDataNotFoundException();
410456
}
411457

412458
$this->output->writeln(' - current configuration:');
413459
$this->displaySqlParams($sqlParams);
414460

415-
$this->output->writeln(' - edit configuration:');
461+
$this->output->writeln(' - edit configuration: (enter \'.\' to skip this step)');
416462
while (true) {
417463
$question = new Question(' . Host: ', '');
418464
$newHost = trim($helper->ask($this->input, $this->output, $question));
419465
if ($newHost !== '') {
420466
break;
421467
}
422468
}
469+
if ($newHost === '.') {
470+
continue;
471+
}
423472

424473
$question = new Question(' . Port: ', '');
425474
$newPort = trim($helper->ask($this->input, $this->output, $question));
475+
if ($newPort === '.') {
476+
continue;
477+
}
426478

427479
while (true) {
428480
$question = new Question(' . Database: ', '');
@@ -431,6 +483,9 @@ private function requestSqlParams(): array {
431483
break;
432484
}
433485
}
486+
if ($newName === '.') {
487+
continue;
488+
}
434489

435490
while (true) {
436491
$question = new Question(' . Username: ', '');
@@ -439,6 +494,9 @@ private function requestSqlParams(): array {
439494
break;
440495
}
441496
}
497+
if ($newUser === '.') {
498+
continue;
499+
}
442500

443501
while (true) {
444502
$question = new Question(' . Password: ', '');
@@ -448,6 +506,9 @@ private function requestSqlParams(): array {
448506
break;
449507
}
450508
}
509+
if ($newPass === '.') {
510+
continue;
511+
}
451512

452513
$newParams = [
453514
ISqlDump::DB_NAME => $newName,
@@ -483,14 +544,28 @@ private function updateConfig(RestoringPoint $point): void {
483544
$dataRoot = $configRoot = '';
484545
foreach ($point->getRestoringData() as $data) {
485546
if ($data->getType() === RestoringData::ROOT_DATA) {
547+
if ($data->getRestoredRoot() === '') {
548+
continue;
549+
}
486550
$dataRoot = $data->getRestoredRoot();
487551
}
488552

489553
if ($data->getType() === RestoringData::FILE_CONFIG) {
554+
if ($data->getRestoredRoot() === '') {
555+
$this->output->writeln(
556+
' * do not refresh as <info>config/config.php</info> were not restored'
557+
);
558+
$this->configService->maintenanceMode(false);
559+
560+
return;
561+
}
490562
$configRoot = $data->getRestoredRoot();
491563
}
492564

493565
if ($data->getType() === RestoringData::FILE_SQL_DUMP) {
566+
if ($data->getRestoredRoot() === '') {
567+
continue;
568+
}
494569
$sqlParams = json_decode($data->getRestoredRoot(), true);
495570
if (!is_array($sqlParams)) {
496571
$sqlParams = [];
@@ -502,17 +577,23 @@ private function updateConfig(RestoringPoint $point): void {
502577
$configFile = rtrim($configRoot, '/') . '/config.php';
503578
include $configFile;
504579

505-
$this->compareConfigDataRoot($CONFIG, $dataRoot);
506-
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_HOST);
507-
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_PORT);
508-
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_NAME);
509-
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_USER);
510-
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_PASS);
580+
if ($dataRoot !== '') {
581+
$this->compareConfigDataRoot($CONFIG, $dataRoot);
582+
}
583+
if (!empty($sqlParams)) {
584+
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_HOST);
585+
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_PORT);
586+
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_NAME);
587+
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_USER);
588+
$this->compareConfigSqlParams($sqlParams, $CONFIG, ISqlDump::DB_PASS);
589+
}
511590

512591
$CONFIG['maintenance'] = false;
513592
$this->output->writeln(' > Updating <info>config.php</info>');
514593
$this->output->writeln('');
515-
file_put_contents($configFile, '<?php' . "\n" . '$CONFIG = ' . var_export($CONFIG, true) . ';' . "\n");
594+
file_put_contents(
595+
$configFile, '<?php' . "\n" . '$CONFIG = ' . var_export($CONFIG, true) . ';' . "\n"
596+
);
516597
}
517598

518599

@@ -593,6 +674,7 @@ private function compareConfigSqlParams(array $sqlParams, array &$CONFIG, string
593674
*
594675
* @throws SqlDumpException
595676
* @throws SqlImportException
677+
* @throws SqlParamsException
596678
*/
597679
private function importSqlDump(RestoringPoint $point, RestoringData $data, array $sqlParams): void {
598680
$chunks = $data->getChunks();
@@ -617,6 +699,8 @@ private function importSqlDump(RestoringPoint $point, RestoringData $data, array
617699

618700
// $config = $this->extractDatabaseConfig();
619701
$sqlDump = $this->pointService->getSqlDump();
702+
$sqlDump->setup($sqlParams);
703+
620704
$sqlDump->import($sqlParams, $read);
621705
}
622706

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
/**
7+
* Nextcloud - Backup now. Restore later.
8+
*
9+
* This file is licensed under the Affero General Public License version 3 or
10+
* later. See the COPYING file.
11+
*
12+
* @author Maxence Lange <maxence@artificial-owl.com>
13+
* @copyright 2021, Maxence Lange <maxence@artificial-owl.com>
14+
* @license GNU AGPL version 3 or any later version
15+
*
16+
* This program is free software: you can redistribute it and/or modify
17+
* it under the terms of the GNU Affero General Public License as
18+
* published by the Free Software Foundation, either version 3 of the
19+
* License, or (at your option) any later version.
20+
*
21+
* This program is distributed in the hope that it will be useful,
22+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+
* GNU Affero General Public License for more details.
25+
*
26+
* You should have received a copy of the GNU Affero General Public License
27+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
28+
*
29+
*/
30+
31+
32+
namespace OCA\Backup\Exceptions;
33+
34+
use Exception;
35+
36+
/**
37+
* Class SqlParamsException
38+
*
39+
* @package OCA\Backup\Exceptions
40+
*/
41+
class SqlParamsException extends Exception {
42+
}

lib/ISqlDump.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
namespace OCA\Backup;
3333

34+
use OCA\Backup\Exceptions\SqlParamsException;
35+
3436
interface ISqlDump {
3537
public const MYSQL = 'mysql';
3638
public const PGSQL = 'pgsql';
@@ -44,19 +46,25 @@ interface ISqlDump {
4446

4547

4648
/**
47-
* @param array $data
49+
* @param array $params
4850
* @param string $filename
51+
*/
52+
public function export(array $params, string $filename): void;
53+
54+
55+
/**
56+
* @param array $params
4957
*
50-
* @return string
58+
* @throws SqlParamsException
5159
*/
52-
public function export(array $data, string $filename): void;
60+
public function setup(array $params): void;
5361

5462

5563
/**
56-
* @param array $data
64+
* @param array $params
5765
* @param resource $read
5866
*
5967
* @return bool
6068
*/
61-
public function import(array $data, $read): bool;
69+
public function import(array $params, $read): bool;
6270
}

0 commit comments

Comments
 (0)