diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c68bb..2f27d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.0.6 - 2021-09-24 +- Exclude `resourcepaths` table from db backup +- Ignore mysql import errors 🤞🏻 +- Better error output + ## 1.0.5 - 2021-05-12 - Update craft-auto-migrate to use `project-config/apply` - Code style cleanup diff --git a/bin/craft-copy-import-db.php b/bin/craft-copy-import-db.php index aa10e3c..3099a88 100755 --- a/bin/craft-copy-import-db.php +++ b/bin/craft-copy-import-db.php @@ -47,25 +47,43 @@ exit(1); } +$credentialsFile = "/tmp/mysql-extra.cnf"; +$credentialsFileContent = [ + "[client]", + "user=" . getenv('DB_USER'), + "password=" . getenv('DB_PASSWORD'), + "host=" . getenv('DB_SERVER') +]; + +if (false === file_put_contents($credentialsFile, join(PHP_EOL, $credentialsFileContent))) { + echo "ERROR: unable to write $credentialsFile"; + exit(1); +} -$cmd = 'mysql -u {DB_USER} -p{DB_PASSWORD} -h {DB_SERVER} {DB_DATABASE} < {file} && echo 1'; $tokens = [ - '{file}' => $file, - '{DB_USER}' => getenv('DB_USER'), - '{DB_PASSWORD}' => getenv('DB_PASSWORD'), - '{DB_SERVER}' => getenv('DB_SERVER'), + '{FILE}' => $file, + '{EXTRA_FILE}' => $credentialsFile, '{DB_DATABASE}' => getenv('DB_DATABASE'), ]; +$cmd = 'mysql --defaults-extra-file={EXTRA_FILE} --force {DB_DATABASE} < {FILE} && echo 1'; $cmd = str_replace(array_keys($tokens), array_values($tokens), $cmd); + $process = \Symfony\Component\Process\Process::fromShellCommandline($cmd); $process->run(); +unlink($credentialsFile); + +if ($stderr = $process->getErrorOutput()) { + fwrite(STDERR, 'ERROR (sql):' . PHP_EOL); + fwrite(STDERR, substr($stderr, 0, 200)); + exit(1); +} + if ($process->isSuccessful()) { echo 'OK'; exit(0); } -echo "ERROR: "; -echo $process->getErrorOutput(); +fwrite(STDERR, 'ERROR (unknown)'); exit(1); diff --git a/src/Actions/DbUpAction.php b/src/Actions/DbUpAction.php index 2e1fd21..6fd4dd8 100644 --- a/src/Actions/DbUpAction.php +++ b/src/Actions/DbUpAction.php @@ -55,6 +55,12 @@ public function run(?string $stage = null) return ExitCode::UNSPECIFIED_ERROR; } + if ($plugin->ssh->exec('ls vendor/bin/craft-copy-import-db.php | wc -l')) { + if (trim($plugin->ssh->getOutput()) !== '1') { + return $this->printAndExit(new PluginNotInstalledException()); + } + } + $bar = $this->createProgressBar($steps); // Step 1: Create dump of the current database @@ -83,13 +89,7 @@ public function run(?string $stage = null) $bar->advance(); $bar->setMessage('Database imported'); } catch (RemoteException $e) { - $this->errorBlock( - [ - 'Unable to import database. Deploy code first using this command:', - 'php craft copy/code/up', - ] - ); - return ExitCode::UNSPECIFIED_ERROR; + return $this->printAndExit($e); } } else { // Step 3: Backup the remote database before importing the uploaded dump @@ -100,21 +100,8 @@ public function run(?string $stage = null) try { $plugin->ssh->exec("php craft copy/db/to-file {$backupFile} --interactive=0"); $bar->advance(); - } catch (CraftNotInstalledException $e) { - $this->errorBlock( - [ - 'Unable to import database. Deploy code first using this command:', - 'php craft copy/code/up', - ] - ); - return ExitCode::UNSPECIFIED_ERROR; - } catch (PluginNotInstalledException $e) { - $this->errorBlock( - [ - 'The plugin seems not to be installed on fortrabbit. Deploy code first using this command:', - 'php craft copy/code/up', - ] - ); + } catch (RemoteException $e) { + return $this->printAndExit($e); } // Step 4: Import on remote @@ -139,4 +126,31 @@ public function run(?string $stage = null) return ExitCode::OK; } + + protected function printAndExit(RemoteException $exception): int + { + if ($exception instanceof CraftNotInstalledException) { + $this->errorBlock( + [ + 'Unable to import database. Deploy code first using this command:', + 'php craft copy/code/up', + ] + ); + return ExitCode::UNSPECIFIED_ERROR; + } + + if ($exception instanceof PluginNotInstalledException) { + $this->errorBlock( + [ + 'The plugin seems not to be installed on fortrabbit. Deploy code first using this command:', + 'php craft copy/code/up', + ] + ); + return ExitCode::UNSPECIFIED_ERROR; + } + + $this->errorBlock([$exception->getMessage()]); + + return ExitCode::UNSPECIFIED_ERROR; + } } diff --git a/src/EventHandlers/IgnoredBackupTablesHandler.php b/src/EventHandlers/IgnoredBackupTablesHandler.php index fb72e59..d313c47 100644 --- a/src/EventHandlers/IgnoredBackupTablesHandler.php +++ b/src/EventHandlers/IgnoredBackupTablesHandler.php @@ -18,9 +18,15 @@ class IgnoredBackupTablesHandler */ public function __invoke(BackupEvent $event): void { - // Since we sync assets, we keep assettransformindex if (property_exists($event, 'ignoreTables')) { - $event->ignoreTables = array_diff($event->ignoreTables, [Table::ASSETTRANSFORMINDEX]); + + // Include assettransformindex (do backup) + $ignoreTables = array_diff($event->ignoreTables, [Table::ASSETTRANSFORMINDEX]); + + // Exclude resourcepaths (don't backup) + $ignoreTables[] = Table::RESOURCEPATHS; + + $event->ignoreTables = $ignoreTables; } } } diff --git a/src/Exceptions/CraftNotInstalledException.php b/src/Exceptions/CraftNotInstalledException.php index fb7dd4f..d61b00d 100644 --- a/src/Exceptions/CraftNotInstalledException.php +++ b/src/Exceptions/CraftNotInstalledException.php @@ -6,4 +6,8 @@ class CraftNotInstalledException extends RemoteException { + /** + * @var string + */ + public $message = 'Craft is not installed on the fortrabbit App.'; } diff --git a/src/Exceptions/RemoteException.php b/src/Exceptions/RemoteException.php index 0419a7e..fb42f6b 100644 --- a/src/Exceptions/RemoteException.php +++ b/src/Exceptions/RemoteException.php @@ -4,8 +4,25 @@ namespace fortrabbit\Copy\Exceptions; +use Throwable; use yii\base\Exception; class RemoteException extends Exception { + public function __construct($message = '', $code = 0, ?Throwable $previous = null) + { + parent::__construct($this->cleanMessage($message), $code, $previous); + } + + protected function cleanMessage(string $message): string + { + // remove double new lines + $message = preg_replace("/[\r\n]+/", "\n", $message); + + // strip after ∙ƒ + if ($endPos = strpos($message, '∙ƒ')) { + $message = substr($message, 0, $endPos); + } + return trim($message); + } } diff --git a/src/Services/Ssh.php b/src/Services/Ssh.php index 7bb9d98..ec30a3c 100644 --- a/src/Services/Ssh.php +++ b/src/Services/Ssh.php @@ -125,14 +125,17 @@ public function exec(string $cmd) return true; } - if (trim($process->getErrorOutput()) === 'Could not open input file') { - throw new CraftNotInstalledException(trim($process->getErrorOutput())); - } + $out = $process->getOutput(); + $err = $process->getOutput(); - if (stristr($process->getErrorOutput(), 'Unknown command')) { - throw new PluginNotInstalledException( - 'The Craft Copy plugin is not installed on remote.' - ); + if (stristr($out, 'Could not open input file')) { + throw new CraftNotInstalledException(); + } + if (stristr($err, 'Could not open input file')) { + throw new CraftNotInstalledException(); + } + if (stristr($err, 'Unknown command')) { + throw new PluginNotInstalledException(); } throw new RemoteException( @@ -140,7 +143,7 @@ public function exec(string $cmd) 'SSH Remote error: ' . $process->getExitCode(), 'Command: ' . $process->getCommandLine(), 'Output:', - $process->getErrorOutput(), + $err, ]) ); }