diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php index f1635d6d4..52b4064fe 100644 --- a/app/Jobs/CleanupHelperContainersJob.php +++ b/app/Jobs/CleanupHelperContainersJob.php @@ -19,6 +19,11 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S public function __construct(public Server $server) {} + private static function helperContainersCommand(): string + { + return 'docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image|test("(^|/)coollabsio/coolify-helper(:|@)")))\''; + } + public function handle(): void { try { @@ -36,7 +41,7 @@ public function handle(): void 'active_deployment_uuids' => $activeDeployments, ]); - $containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("'.coolifyRegistryUrl().'/coollabsio/coolify-helper")))\''], $this->server, false); + $containers = instant_remote_process_with_timeout([self::helperContainersCommand()], $this->server, false); $helperContainers = collect(json_decode($containers)); if ($helperContainers->count() > 0) { diff --git a/tests/Unit/CleanupHelperContainersJobTest.php b/tests/Unit/CleanupHelperContainersJobTest.php new file mode 100644 index 000000000..bd3e2e175 --- /dev/null +++ b/tests/Unit/CleanupHelperContainersJobTest.php @@ -0,0 +1,74 @@ +invoke(null); + $directory = sys_get_temp_dir().'/coolify-helper-filter-'.bin2hex(random_bytes(4)); + $docker = $directory.'/docker'; + $images = [ + 'coollabsio/coolify-helper:1.0.15', + 'docker.io/coollabsio/coolify-helper:1.0.16', + 'ghcr.io/coollabsio/coolify-helper@sha256:abc', + 'registry.example/team/coollabsio/coolify-helper:latest', + 'coollabsio/coolify:latest', + 'coollabsio/coolify:4.3.12', + 'coollabsio/coolify-realtime:1.0.10', + 'coolify-helper:latest', + 'someone/coolify-helper:1.0.16', + 'evil/coollabsio/coolify-helper-copy:latest', + 'coollabsio/not-coolify-helper:latest', + ]; + + mkdir($directory); + file_put_contents($docker, "#!/bin/sh\n".implode("\n", array_map( + fn (string $image): string => 'echo '.escapeshellarg(json_encode(['Image' => $image], JSON_THROW_ON_ERROR)), + $images + ))."\n"); + chmod($docker, 0755); + + try { + $process = new Process(['/bin/sh', '-c', $command], env: [ + 'PATH' => $directory.':'.getenv('PATH'), + ]); + $process->mustRun(); + + expect(array_column(json_decode($process->getOutput(), true, flags: JSON_THROW_ON_ERROR), 'Image')) + ->toBe(array_slice($images, 0, 4)); + } finally { + unlink($docker); + rmdir($directory); + } +}); + +it('preserves the helper image filter for non-root servers', function () { + $command = (new ReflectionMethod(CleanupHelperContainersJob::class, 'helperContainersCommand'))->invoke(null); + $server = Mockery::mock(Server::class)->makePartial(); + $server->user = 'ubuntu'; + $command = parseCommandsByLineForSudo(collect([$command]), $server)[0]; + $directory = sys_get_temp_dir().'/coolify-helper-sudo-filter-'.bin2hex(random_bytes(4)); + $docker = $directory.'/docker'; + $sudo = $directory.'/sudo'; + + mkdir($directory); + file_put_contents($docker, "#!/bin/sh\necho '{\"Image\":\"coollabsio/coolify-helper:1.0.15\"}'\n"); + file_put_contents($sudo, "#!/bin/sh\nexec \"\$@\"\n"); + chmod($docker, 0755); + chmod($sudo, 0755); + + try { + $process = new Process(['/bin/sh', '-c', $command], env: [ + 'PATH' => $directory.':'.getenv('PATH'), + ]); + $process->mustRun(); + + expect(array_column(json_decode($process->getOutput(), true, flags: JSON_THROW_ON_ERROR), 'Image')) + ->toBe(['coollabsio/coolify-helper:1.0.15']); + } finally { + unlink($docker); + unlink($sudo); + rmdir($directory); + } +});