diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 794c581011..fbe26dd586 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -106,6 +106,14 @@ class GetContainersStatus $foundDatabases = []; $foundServices = []; + // Owners of preview containers outside this server's applications, in one query. + $foreignApplicationIdsByUuid = containerApplicationIdsByUuid( + $this->applications, + $this->containers + ->map(fn ($container) => format_docker_labels_to_json(data_get($container, $this->server->isSwarm() ? 'Spec.Labels' : 'Config.Labels') ?? [])) + ->filter(fn (Collection $labels) => (bool) $labels->get('coolify.pullRequestId') && isContainerOfType($labels, 'application')) + ); + foreach ($this->containers as $container) { if ($this->server->isSwarm()) { $labels = data_get($container, 'Spec.Labels'); @@ -136,7 +144,7 @@ class GetContainersStatus if ($isApplicationContainer) { $pullRequestId = data_get($labels, 'coolify.pullRequestId'); if ($pullRequestId) { - $applicationId = resolveContainerApplicationId($this->applications, $flatLabels); + $applicationId = resolveContainerApplicationId($this->applications, $flatLabels, $foreignApplicationIdsByUuid); $preview = $applicationId ? ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first() : null; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 8883cd0cab..b44714a8b0 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -154,14 +154,16 @@ function resolveContainerApplicationId(Collection $applications, Collection|arra */ function containerApplicationIdsByUuid(Collection $applications, iterable $containerLabels): array { - $knownUuids = $applications->pluck('uuid')->flip(); $uuids = []; foreach ($containerLabels as $labels) { $uuid = containerOwnerUuid($labels, 'application'); - if ($uuid && ! $knownUuids->has($uuid)) { + if ($uuid) { $uuids[$uuid] = true; } } + foreach ($applications->pluck('uuid')->filter() as $knownUuid) { + unset($uuids[$knownUuid]); + } if ($uuids === []) { return []; } diff --git a/tests/Feature/ContainerOwnerUuidLabelsTest.php b/tests/Feature/ContainerOwnerUuidLabelsTest.php index 67957a181d..5e46f7bab2 100644 --- a/tests/Feature/ContainerOwnerUuidLabelsTest.php +++ b/tests/Feature/ContainerOwnerUuidLabelsTest.php @@ -311,6 +311,57 @@ describe('status updates after a server transfer', function () { expect($this->application->fresh()->status)->toStartWith('running'); }); + + test('container status check looks up foreign preview owners with one query for all containers', function () { + $deleted = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + $preview = ApplicationPreview::forceCreate([ + 'application_id' => $deleted->id, + 'pull_request_id' => 3, + 'pull_request_html_url' => 'https://example.com/pr/3', + 'status' => 'exited', + ]); + $deleted->delete(); + + $containers = collect(range(1, 5))->map(fn (int $i) => [ + 'Name' => "/foreign-app-{$i}-pr-{$i}", + 'State' => ['Status' => 'running', 'Health' => ['Status' => 'healthy']], + 'RestartCount' => 0, + 'Config' => ['Labels' => [ + 'coolify.managed' => 'true', + 'coolify.applicationUuid' => "foreign-app-{$i}", + 'coolify.pullRequestId' => (string) $i, + 'com.docker.compose.service' => "foreign-app-{$i}-pr-{$i}", + ]], + ])->push([ + 'Name' => '/'.$deleted->uuid.'-pr-3', + 'State' => ['Status' => 'running', 'Health' => ['Status' => 'healthy']], + 'RestartCount' => 0, + 'Config' => ['Labels' => [ + 'coolify.managed' => 'true', + 'coolify.applicationUuid' => $deleted->uuid, + 'coolify.pullRequestId' => '3', + 'com.docker.compose.service' => $deleted->uuid.'-pr-3', + ]], + ]); + + $this->server->settings()->update(['is_reachable' => true, 'is_usable' => true]); + $server = $this->server->fresh(); + + DB::enableQueryLog(); + GetContainersStatus::run($server, $containers, collect()); + $ownerLookups = collect(DB::getQueryLog()) + ->filter(fn (array $query) => str_contains($query['query'], 'from "applications"') && preg_match('/"uuid" (=|in) /', $query['query']) === 1) + ->count(); + DB::disableQueryLog(); + + expect($ownerLookups)->toBe(1) + // The preview of a trashed owner still resolves. + ->and($preview->fresh()->status)->toBe('running:healthy'); + }); }); describe('orphaned preview cleanup', function () {