diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6d42398c21..99ff1ac874 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -671,7 +671,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue [executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName] ); if ($this->saved_outputs->has($saveName)) { - $fileStat = $this->saved_outputs->get($saveName); + $fileStat = $this->trimmedSavedOutput($saveName); if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) { $fileStorage->is_directory = true; $fileStorage->content = null; @@ -2171,12 +2171,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}"); } - if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') { + $healthCheckStatus = $this->trimmedSavedOutput('health_check')->replace('"', '')->value(); + if ($healthCheckStatus === 'healthy') { $this->newVersionIsHealthy = true; $this->application->update(['status' => 'running']); $this->application_deployment_queue->addLogEntry('New container is healthy.'); break; - } elseif (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') { + } elseif ($healthCheckStatus === 'unhealthy') { $this->newVersionIsHealthy = false; $this->application_deployment_queue->addLogEntry('New container is unhealthy.', type: 'error'); $this->query_logs(); @@ -2189,7 +2190,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $sleeptime++; } } - if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') { + if ($this->trimmedSavedOutput('health_check')->replace('"', '')->value() === 'starting') { $this->query_logs(); } } @@ -2571,7 +2572,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue ] ); if ($this->saved_outputs->get('commit_message')) { - $commit_message = str($this->saved_outputs->get('commit_message')); + $commit_message = $this->trimmedSavedOutput('commit_message'); $this->application_deployment_queue->commit_message = $commit_message->value(); ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update( ['commit_message' => $commit_message->value()] @@ -2641,7 +2642,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue [executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), 'save' => 'nixpacks_type', 'hidden' => true], ); if ($this->saved_outputs->get('nixpacks_type')) { - $this->nixpacks_type = $this->saved_outputs->get('nixpacks_type'); + $this->nixpacks_type = $this->trimmedSavedOutput('nixpacks_type')->value(); if (str($this->nixpacks_type)->isEmpty()) { throw new DeploymentException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers'); } diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 5c709f5494..e012663e71 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -9,6 +9,7 @@ use App\Models\Server; use Carbon\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Process; +use Illuminate\Support\Stringable; trait ExecuteRemoteCommand { @@ -247,7 +248,12 @@ trait ExecuteRemoteCommand return; } - $this->saved_outputs->put($this->save, str($output)); + $this->saved_outputs->put($this->save, str($output)->trim()); + } + + private function trimmedSavedOutput(string $key): Stringable + { + return str($this->saved_outputs->get($key))->trim(); } /** diff --git a/config/constants.php b/config/constants.php index f06e3d065a..38fc7b98cb 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => env('COOLIFY_VERSION') ?: '4.3.22', + 'version' => env('COOLIFY_VERSION') ?: '4.3.23', 'helper_version' => '1.0.17', 'realtime_version' => '1.0.19', 'railpack_version' => '0.23.0', diff --git a/other/nightly/versions.json b/other/nightly/versions.json index ffc5266d26..440716d24e 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.3.22" + "version": "4.3.23" }, "nightly": { "version": "4.4-rc.1" diff --git a/tests/Unit/ExecuteRemoteCommandTest.php b/tests/Unit/ExecuteRemoteCommandTest.php index a7b8008d45..40e0969338 100644 --- a/tests/Unit/ExecuteRemoteCommandTest.php +++ b/tests/Unit/ExecuteRemoteCommandTest.php @@ -23,6 +23,11 @@ function remoteCommandOutputCollector(): object { $this->saveCommandOutput($output, $append); } + + public function trimmedOutput(string $key = 'dockerfile'): string + { + return $this->trimmedSavedOutput($key)->value(); + } }; } @@ -37,11 +42,28 @@ it('preserves whitespace across streamed saved output chunks', function () { ->toBe("FROM alpine\nARG FIRST\nARG SECOND\nRUN true\n"); }); -it('replaces saved output without trimming it when append is disabled', function () { +it('trims saved output when append is disabled', function () { $collector = remoteCommandOutputCollector(); $collector->collectOutput('old'); $collector->collectOutput(" new output\n", append: false); - expect((string) $collector->saved_outputs->get('dockerfile'))->toBe(" new output\n"); + expect((string) $collector->saved_outputs->get('dockerfile'))->toBe('new output'); +}); + +it('keeps non-appended command output safe for exact status comparisons', function () { + $collector = remoteCommandOutputCollector(); + + $collector->collectOutput("\"healthy\"\n", append: false); + + expect(str($collector->saved_outputs->get('dockerfile'))->replace('"', '')->value()) + ->toBe('healthy'); +}); + +it('normalizes streamed scalar output without changing the saved value', function () { + $collector = remoteCommandOutputCollector(); + $collector->collectOutput("node\n"); + + expect($collector->trimmedOutput())->toBe('node') + ->and((string) $collector->saved_outputs->get('dockerfile'))->toBe("node\n"); }); diff --git a/tests/Unit/ProductionImageWorkflowTest.php b/tests/Unit/ProductionImageWorkflowTest.php index ecc594d547..4d32f604fe 100644 --- a/tests/Unit/ProductionImageWorkflowTest.php +++ b/tests/Unit/ProductionImageWorkflowTest.php @@ -25,8 +25,8 @@ it('publishes v4 branch builds under the commit sha with a traceable internal ve ->toContain('ARG COOLIFY_VERSION') ->toContain('ENV COOLIFY_VERSION=${COOLIFY_VERSION}') ->and($constants) - ->toContain("'version' => env('COOLIFY_VERSION') ?: '4.3.22'") - ->and($versions['coolify']['v4']['version'])->toBe('4.3.22') + ->toContain("'version' => env('COOLIFY_VERSION') ?: '4.3.23'") + ->and($versions['coolify']['v4']['version'])->toBe('4.3.23') ->and($versions['coolify']['nightly']['version'])->toBe('4.4-rc.1') ->and($nightlyVersions)->toBe($versions); }); diff --git a/versions.json b/versions.json index ffc5266d26..440716d24e 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.3.22" + "version": "4.3.23" }, "nightly": { "version": "4.4-rc.1"