]> BookStack Code Mirror - system-cli/commitdiff
Range of changes to MySQL execution
authorDan Brown <redacted>
Sat, 8 Mar 2025 17:19:58 +0000 (17:19 +0000)
committerDan Brown <redacted>
Sat, 8 Mar 2025 17:19:58 +0000 (17:19 +0000)
- 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.

src/Commands/RestoreCommand.php
src/Services/MySqlRunner.php
src/Services/ProgramRunner.php

index 3fb91540dc8328696ba269741b6714579a5d3deb..4bbdbc8e11fc4cbd686407389f09320b13f99d1e 100644 (file)
@@ -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
index f9110695a50371a81fa9f482f2b9d752ffa4a0dc..a3e1033d69d6cda16f22c9913d40c0f611d2f43f 100644 (file)
@@ -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
+}
index 5baa2b387de0a0be52d2495b3b908bf71b57d47a..df1b815f090410bdf7cc6fe3e49de9f5db8a56a4 100644 (file)
@@ -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.");
     }
 }