diff --git a/app/Jobs/ExecuteCoolifyProcess.php b/app/Jobs/ExecuteCoolifyProcess.php index 8dca3bc00..e6e6898e6 100755 --- a/app/Jobs/ExecuteCoolifyProcess.php +++ b/app/Jobs/ExecuteCoolifyProcess.php @@ -2,35 +2,20 @@ namespace App\Jobs; -use App\Services\ProcessStatus; -use Illuminate\Support\Facades\DB; +use App\Services\RemoteProcess\RemoteProcess; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Process\InvokedProcess; use Illuminate\Process\ProcessResult; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Process; use Spatie\Activitylog\Contracts\Activity; class ExecuteCoolifyProcess implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $throttleIntervalMS = 500; - - protected $timeStart; - - protected $currentTime; - - protected $lastWriteAt = 0; - - protected string $stdOutIncremental = ''; - - protected string $stdErrIncremental = ''; - /** * Create a new job instance. */ @@ -41,92 +26,12 @@ class ExecuteCoolifyProcess implements ShouldQueue /** * Execute the job. */ - public function handle(): ProcessResult + public function handle(): void { - $this->timeStart = hrtime(true); - - $user = $this->activity->getExtraProperty('user'); - $destination = $this->activity->getExtraProperty('destination'); - $port = $this->activity->getExtraProperty('port'); - $command = $this->activity->getExtraProperty('command'); - - $delimiter = 'EOF-COOLIFY-SSH'; - - $sshCommand = 'ssh ' - . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' - . '-o PasswordAuthentication=no ' - . '-o RequestTTY=no ' - // Quiet mode. Causes most warning and diagnostic messages to be suppressed. - // Errors are still out put. This is to silence for example, that warning - // Permanently added to the list of known hosts. - . '-q ' - . "-p {$port} " - . "{$user}@{$destination} " - . " 'bash -se' << \\$delimiter" . PHP_EOL - . $command . PHP_EOL - . $delimiter; - - $process = Process::start($sshCommand, $this->handleOutput(...)); - - $processResult = $process->wait(); - - $status = match ($processResult->exitCode()) { - 0 => ProcessStatus::FINISHED, - default => ProcessStatus::ERROR, - }; - - $this->activity->properties = $this->activity->properties->merge([ - 'exitCode' => $processResult->exitCode(), - 'stdout' => $processResult->output(), - 'stderr' => $processResult->errorOutput(), - 'status' => $status, + $remoteProcess = resolve(RemoteProcess::class, [ + 'activity' => $this->activity, ]); - $this->activity->save(); - - return $processResult; - } - - protected function handleOutput(string $type, string $output) - { - $this->currentTime = $this->elapsedTime(); - - if ($type === 'out') { - $this->stdOutIncremental .= $output; - } else { - $this->stdErrIncremental .= $output; - } - - $this->activity->description .= $output; - - if ($this->isAfterLastThrottle()) { - // Let's write to database. - DB::transaction(function () { - $this->activity->save(); - $this->lastWriteAt = $this->currentTime; - }); - } - } - - /** - * Decides if it's time to write again to database. - * - * @return bool - */ - protected function isAfterLastThrottle() - { - // If DB was never written, then we immediately decide we have to write. - if ($this->lastWriteAt === 0) { - return true; - } - - return ($this->currentTime - $this->throttleIntervalMS) > $this->lastWriteAt; - } - - protected function elapsedTime(): int - { - $timeMs = (hrtime(true) - $this->timeStart) / 1_000_000; - - return intval($timeMs); + $remoteProcess(); } } diff --git a/app/Services/CoolifyProcess.php b/app/Services/CoolifyProcess.php index 16071ebd7..cba46ebac 100644 --- a/app/Services/CoolifyProcess.php +++ b/app/Services/CoolifyProcess.php @@ -27,17 +27,13 @@ class CoolifyProcess 'command' => $this->command, 'status' => ProcessStatus::HOLDING, ]) - ->log("Awaiting to start command...\n\n"); + ->log("Awaiting command to start...\n\n"); } - public function __invoke(): Activity|ProcessResult + public function __invoke(): Activity { $job = new ExecuteCoolifyProcess($this->activity); - if (app()->environment('testing')) { - return $job->handle(); - } - dispatch($job); return $this->activity; diff --git a/app/Services/RemoteProcess/RemoteProcess.php b/app/Services/RemoteProcess/RemoteProcess.php new file mode 100644 index 000000000..ce83a9769 --- /dev/null +++ b/app/Services/RemoteProcess/RemoteProcess.php @@ -0,0 +1,121 @@ +timeStart = hrtime(true); + + $processResult = Process::run($this->getCommand(), $this->handleOutput(...)); + + $status = match ($processResult->exitCode()) { + 0 => ProcessStatus::FINISHED, + default => ProcessStatus::ERROR, + }; + + $this->activity->properties = $this->activity->properties->merge([ + 'exitCode' => $processResult->exitCode(), + 'stdout' => $processResult->output(), + 'stderr' => $processResult->errorOutput(), + 'status' => $status, + ]); + + $this->activity->save(); + + return $processResult; + } + + protected function getCommand(): string + { + $user = $this->activity->getExtraProperty('user'); + $destination = $this->activity->getExtraProperty('destination'); + $port = $this->activity->getExtraProperty('port'); + $command = $this->activity->getExtraProperty('command'); + + $delimiter = 'EOF-COOLIFY-SSH'; + + return 'ssh ' + . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + . '-o PasswordAuthentication=no ' + . '-o RequestTTY=no ' + // Quiet mode. Causes most warning and diagnostic messages to be suppressed. + // Errors are still out put. This is to silence for example, that warning + // Permanently added to the list of known hosts. + . '-q ' + . "-p {$port} " + . "{$user}@{$destination} " + . " 'bash -se' << \\$delimiter" . PHP_EOL + . $command . PHP_EOL + . $delimiter; + } + + protected function handleOutput(string $type, string $output) + { + $this->currentTime = $this->elapsedTime(); + + if ($type === 'out') { + $this->stdOutIncremental .= $output; + } else { + $this->stdErrIncremental .= $output; + } + + $this->activity->description .= $output; + + if ($this->isAfterLastThrottle()) { + // Let's write to database. + DB::transaction(function () { + $this->activity->save(); + $this->lastWriteAt = $this->currentTime; + }); + } + } + + /** + * Determines if it's time to write again to database. + * + * @return bool + */ + protected function isAfterLastThrottle() + { + // If DB was never written, then we immediately decide we have to write. + if ($this->lastWriteAt === 0) { + return true; + } + + return ($this->currentTime - $this->throttleIntervalMS) > $this->lastWriteAt; + } + + protected function elapsedTime(): int + { + $timeMs = (hrtime(true) - $this->timeStart) / 1_000_000; + + return intval($timeMs); + } +} diff --git a/bootstrap/helpers.php b/bootstrap/helpers.php index 0d48f5401..4d26314f2 100644 --- a/bootstrap/helpers.php +++ b/bootstrap/helpers.php @@ -1,7 +1,6 @@ $destination, diff --git a/resources/views/livewire/run-command.blade.php b/resources/views/livewire/run-command.blade.php index 370264e6c..027eb14f0 100755 --- a/resources/views/livewire/run-command.blade.php +++ b/resources/views/livewire/run-command.blade.php @@ -56,8 +56,7 @@ flex-direction: column-reverse; " placeholder="Build output" - > - {{ data_get($activity, 'description') }} + >{{ data_get($activity, 'description') }}
diff --git a/tests/Feature/DockerCommandsTest.php b/tests/Feature/DockerCommandsTest.php index 32340a70d..45a2f1f64 100644 --- a/tests/Feature/DockerCommandsTest.php +++ b/tests/Feature/DockerCommandsTest.php @@ -13,20 +13,21 @@ it('starts a docker container correctly', function () { $host = 'testing-host'; // Assert there's no containers start with coolify_test_* - $processResult = coolifyProcess($areThereCoolifyTestContainers, $host); - $containers = Output::containerList($processResult->output()); + $activity = coolifyProcess($areThereCoolifyTestContainers, $host); + ray($activity); + $containers = Output::containerList($activity->getExtraProperty('stdout')); expect($containers)->toBeEmpty(); // start a container nginx -d --name = $containerName - $processResult = coolifyProcess("docker run -d --name {$containerName} nginx", $host); - expect($processResult->successful())->toBeTrue(); + $activity = coolifyProcess("docker run -d --name {$containerName} nginx", $host); + expect($activity->getExtraProperty('exitCode'))->toBe(0); // docker ps name = $container - $processResult = coolifyProcess($areThereCoolifyTestContainers, $host); - $containers = Output::containerList($processResult->output()); + $activity = coolifyProcess($areThereCoolifyTestContainers, $host); + $containers = Output::containerList($activity->getExtraProperty('stdout')); expect($containers->where('Names', $containerName)->count())->toBe(1); // Stop testing containers - $processResult = coolifyProcess("docker stop $(docker ps --filter='name={$coolifyNamePrefix}*' -q)", $host); - expect($processResult->successful())->toBeTrue(); + $activity = coolifyProcess("docker stop $(docker ps --filter='name={$coolifyNamePrefix}*' -q)", $host); + expect($activity->getExtraProperty('exitCode'))->toBe(0); });