From: Dan Brown Date: Sat, 8 Mar 2025 17:19:58 +0000 (+0000) Subject: Range of changes to MySQL execution X-Git-Url: http://source.bookstackapp.com/system-cli/commitdiff_plain/b0e4bd51db4d0faa12400b83b11e9a369c4bad8c Range of changes to MySQL execution - Changes mysql usage to use options files, removes used of env options used for password. - Updated to use mariadb specific programs if existing, otherwise mysql. - Adds common xampp bin path for searching for mysql. --- diff --git a/src/Commands/RestoreCommand.php b/src/Commands/RestoreCommand.php index 3fb9154..4bbdbc8 100644 --- a/src/Commands/RestoreCommand.php +++ b/src/Commands/RestoreCommand.php @@ -134,7 +134,7 @@ class RestoreCommand extends Command protected function restoreEnv(string $extractDir, string $appDir, OutputInterface $output, InteractiveConsole $interactions): array { - $oldEnv = EnvironmentLoader::load($extractDir); + $oldEnv = EnvironmentLoader::loadMergedWithCurrentEnv($extractDir); $currentEnv = EnvironmentLoader::load($appDir); $envContents = file_get_contents(Paths::join($extractDir, '.env')); $appEnvPath = Paths::real(Paths::join($appDir, '.env')); @@ -192,7 +192,7 @@ class RestoreCommand extends Command protected function restoreDatabase(string $appDir, string $extractDir): void { $dbDump = Paths::join($extractDir, 'db.sql'); - $currentEnv = EnvironmentLoader::load($appDir); + $currentEnv = EnvironmentLoader::loadMergedWithCurrentEnv($appDir); $mysql = MySqlRunner::fromEnvOptions($currentEnv); // Drop existing tables diff --git a/src/Services/MySqlRunner.php b/src/Services/MySqlRunner.php index f911069..a3e1033 100644 --- a/src/Services/MySqlRunner.php +++ b/src/Services/MySqlRunner.php @@ -3,6 +3,7 @@ namespace Cli\Services; use Exception; +use Symfony\Component\Process\Exception\ProcessTimedOutException; class MySqlRunner { @@ -28,41 +29,63 @@ class MySqlRunner } } + protected function createOptionsFile(): string + { + $path = tempnam(sys_get_temp_dir(), 'bs-cli-mysql-opts'); + $contents = "[client]\nuser={$this->user}\nhost={$this->host}\nport={$this->port}\npassword={$this->password}\nprotocol=TCP"; + file_put_contents($path, $contents); + chmod($path, 0600); + return $path; + } + + protected function getProgramRunnerInstance(): ProgramRunner + { + return (new ProgramRunner(['mariadb', 'mysql'], '/usr/bin/mysql')); + } + public function testConnection(): bool { - $output = (new ProgramRunner('mysql', '/usr/bin/mysql')) - ->withEnvironment(['MYSQL_PWD' => $this->password]) - ->withTimeout(300) - ->withIdleTimeout(300) - ->runCapturingStdErr([ - '-h', $this->host, - '-P', $this->port, - '-u', $this->user, - '--protocol=TCP', - $this->database, - '-e', "show tables;" - ]); - - return !$output; + $optionsFile = $this->createOptionsFile(); + + try { + $stdErr = $this->getProgramRunnerInstance() + ->withTimeout(300) + ->withIdleTimeout(300) + ->runCapturingStdErr([ + "--defaults-file={$optionsFile}", + $this->database, + '-e', "show tables;" + ]); + unlink($optionsFile); + } catch (Exception $exception) { + unlink($optionsFile); + throw $exception; + } + + return !$stdErr; } public function importSqlFile(string $sqlFilePath): void { - $output = (new ProgramRunner('mysql', '/usr/bin/mysql')) - ->withEnvironment(['MYSQL_PWD' => $this->password]) - ->withTimeout(300) - ->withIdleTimeout(300) - ->runCapturingStdErr([ - '-h', $this->host, - '-P', $this->port, - '-u', $this->user, - '--protocol=TCP', - $this->database, - '-e', "source {$sqlFilePath}" - ]); + $optionsFile = $this->createOptionsFile(); + + try { + $output = $this->getProgramRunnerInstance() + ->withTimeout(300) + ->withIdleTimeout(300) + ->runCapturingStdErr([ + "--defaults-file={$optionsFile}", + $this->database, + '-e', "source {$sqlFilePath}" + ]); + unlink($optionsFile); + } catch (Exception $exception) { + unlink($optionsFile); + throw $exception; + } if ($output) { - throw new Exception("Failed mysql file import with errors:\n" . $output); + throw new Exception("Failed mysql file import with errors:\n{$output}"); } } @@ -91,17 +114,15 @@ HEREDOC; $errors = ""; $warnings = ""; $hasOutput = false; + $optionsFile = $this->createOptionsFile(); try { - (new ProgramRunner('mysqldump', '/usr/bin/mysqldump')) + (new ProgramRunner(['mariadb-dump', 'mysqldump'], '/usr/bin/mysqldump')) ->withTimeout(300) ->withIdleTimeout(300) - ->withEnvironment(['MYSQL_PWD' => $this->password]) + ->withAdditionalPathLocation('C:\xampp\mysql\bin') ->runWithoutOutputCallbacks([ - '-h', $this->host, - '-P', $this->port, - '-u', $this->user, - '--protocol=TCP', + "--defaults-file={$optionsFile}", '--single-transaction', '--no-tablespaces', $this->database, @@ -113,13 +134,15 @@ HEREDOC; foreach ($lines as $line) { if (str_starts_with(strtolower($line), 'warning: ')) { $warnings .= $line; - } else { + } else if (!empty(trim($line))) { $errors .= $line . "\n"; } } }); + unlink($optionsFile); } catch (\Exception $exception) { fclose($file); + unlink($optionsFile); if ($exception instanceof ProcessTimedOutException) { if (!$hasOutput) { throw new Exception("mysqldump operation timed-out.\nNo data has been received so the connection to your database may have failed."); @@ -149,4 +172,4 @@ HEREDOC; return new static($host, $username, $password, $database, $port); } -} \ No newline at end of file +} diff --git a/src/Services/ProgramRunner.php b/src/Services/ProgramRunner.php index 5baa2b3..df1b815 100644 --- a/src/Services/ProgramRunner.php +++ b/src/Services/ProgramRunner.php @@ -2,20 +2,28 @@ namespace Cli\Services; +use Exception; use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; class ProgramRunner { + /** + * Names of the program to search for. + * @var string[] + */ + protected array $programNames = []; + protected int $timeout = 240; protected int $idleTimeout = 15; protected array $environment = []; protected array $additionalProgramDirectories = []; public function __construct( - protected string $program, + string|array $program, protected string $defaultPath ) { + $this->programNames = is_string($program) ? [$program] : $program; } public function withTimeout(int $timeoutSeconds): static @@ -81,7 +89,7 @@ class ProgramRunner } /** - * @throws \Exception + * @throws Exception */ public function ensureFound(): void { @@ -93,7 +101,7 @@ class ProgramRunner try { $this->ensureFound(); return true; - } catch (\Exception $exception) { + } catch (Exception $exception) { return false; } } @@ -108,15 +116,21 @@ class ProgramRunner return $process; } + /** + * @throws Exception + */ protected function resolveProgramPath(): string { - $executableFinder = new ExecutableFinder(); - $path = $executableFinder->find($this->program, $this->defaultPath, $this->additionalProgramDirectories); + foreach ($this->programNames as $programName) { + $executableFinder = new ExecutableFinder(); + $path = $executableFinder->find($programName, $this->defaultPath, $this->additionalProgramDirectories); - if (is_null($path) || !is_file($path)) { - throw new \Exception("Could not locate \"{$this->program}\" program."); + if (!is_null($path) && is_file($path)) { + return $path; + } } - return $path; + $combinedProgram = implode('|', $this->programNames); + throw new Exception("Could not locate \"{$combinedProgram}\" program."); } }