mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(docker): match legacy Compose containers by UUID in names
Resolve application owners from dash-separated UUIDs in Compose service or coolify.name labels, and include service-prefixed names in Docker filters. Add regression coverage for custom Compose start commands, status updates, and UUID boundary matching.
This commit is contained in:
@@ -89,10 +89,12 @@ function containerOwnerUuid(Collection|array|string $labels, string $type): ?str
|
||||
|
||||
/**
|
||||
* Resource that owns a container, from the given candidates, by UUID label or compose project /
|
||||
* stack namespace (see containerOwnerUuid()). Applications deployed before mid-2024 used the
|
||||
* deployment directory as compose project; their compose service (the container name) starts
|
||||
* with the application UUID. The numeric id label is never used: another Coolify instance that
|
||||
* manages the same server can have a resource with the same id.
|
||||
* stack namespace (see containerOwnerUuid()). Some older application containers have another
|
||||
* compose project: the deployment directory (before mid-2024, or a custom Compose start command
|
||||
* without --project-name). Their container name (`coolify.name`, for Dockerfile-style builds also
|
||||
* the compose service) contains the application UUID as a dash-separated part, for example
|
||||
* `{uuid}-104512123456` or `{service}-{uuid}-104512123456`. The numeric id label is never used:
|
||||
* another Coolify instance that manages the same server can have a resource with the same id.
|
||||
*
|
||||
* @template TResource of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
@@ -110,9 +112,11 @@ function resolveContainerOwner(Collection $resources, Collection|array|string $l
|
||||
return $owner;
|
||||
}
|
||||
|
||||
$composeService = (string) $labels->get('com.docker.compose.service');
|
||||
$names = [(string) $labels->get('com.docker.compose.service'), (string) $labels->get('coolify.name')];
|
||||
|
||||
return $resources->first(fn ($resource) => $composeService === $resource->uuid || str_starts_with($composeService, $resource->uuid.'-'));
|
||||
return $resources->first(fn ($resource) => collect($names)->contains(
|
||||
fn (string $name) => filled($resource->uuid) && preg_match('/(?:^|-)'.preg_quote($resource->uuid, '/').'(?:-|$)/', $name) === 1
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,8 +242,8 @@ function dockerPsByOwnerCommands(string $type, string $uuid, array $extraFilters
|
||||
$base.' '.$filters(["label=coolify.{$type}Id", "label=com.docker.compose.project={$uuid}", ...$legacyFilters]).' '.$formatArgument,
|
||||
];
|
||||
if ($type === 'application') {
|
||||
// Deployed before mid-2024 (see resolveContainerOwner()): the container name starts with the UUID.
|
||||
$commands[] = $base.' '.$filters(['label=coolify.applicationId', "name=^{$uuid}", ...$legacyFilters]).' '.$formatArgument;
|
||||
// Compose project is the deployment directory (see resolveContainerOwner()): the container name contains the UUID.
|
||||
$commands[] = $base.' '.$filters(['label=coolify.applicationId', "name={$uuid}", ...$legacyFilters]).' '.$formatArgument;
|
||||
}
|
||||
|
||||
return $commands;
|
||||
|
||||
@@ -151,6 +151,10 @@ describe('owner resolution', function () {
|
||||
// Deployed before mid-2024: compose project was the deployment directory, the compose service starts with the UUID.
|
||||
->and(resolveContainerOwner($applications, ['coolify.applicationId' => '999999', 'com.docker.compose.project' => 'deployment-dir', 'com.docker.compose.service' => $this->application->uuid.'-104512123456'], 'application')?->id)->toBe($this->application->id)
|
||||
->and(resolveContainerOwner($applications, ['coolify.applicationId' => $localId, 'com.docker.compose.project' => 'deployment-dir', 'com.docker.compose.service' => 'other-instance-uuid-104512123456'], 'application'))->toBeNull()
|
||||
// Compose application with a custom start command: the compose project is the deployment directory, the container name is `{service}-{uuid}-{suffix}`.
|
||||
->and(resolveContainerOwner($applications, ['coolify.applicationId' => '999999', 'com.docker.compose.project' => 'deployment-dir', 'com.docker.compose.service' => 'public-api', 'coolify.name' => 'public-api-'.$this->application->uuid.'-104512123456'], 'application')?->id)->toBe($this->application->id)
|
||||
->and(resolveContainerOwner($applications, ['coolify.applicationId' => $localId, 'com.docker.compose.project' => 'deployment-dir', 'com.docker.compose.service' => 'public-api', 'coolify.name' => 'public-api-other-instance-uuid-104512123456'], 'application'))->toBeNull()
|
||||
->and(resolveContainerOwner($applications, ['coolify.applicationId' => $localId, 'com.docker.compose.project' => 'deployment-dir', 'coolify.name' => 'public-api-'.$this->application->uuid.'x-104512123456'], 'application'))->toBeNull()
|
||||
->and(implode("\n", dockerPsByOwnerCommands('application', 'app-uuid')))->not->toContain('label=coolify.applicationId=');
|
||||
});
|
||||
|
||||
@@ -163,9 +167,9 @@ describe('owner resolution', function () {
|
||||
->and($commands[1])->toContain("--filter 'label=coolify.applicationId'")
|
||||
->toContain("--filter 'label=com.docker.compose.project=app-uuid'")
|
||||
->toContain("--filter 'label=coolify.pullRequestId=0'")
|
||||
// Deployed before mid-2024: the container name starts with the application UUID.
|
||||
// Compose project is the deployment directory: the container name contains the application UUID.
|
||||
->and($commands[2])->toContain("--filter 'label=coolify.applicationId'")
|
||||
->toContain("--filter 'name=^app-uuid'")
|
||||
->toContain("--filter 'name=app-uuid'")
|
||||
->toContain("--filter 'label=coolify.pullRequestId=0'")
|
||||
->and(dockerPsByOwnerCommands('service', 'service-uuid'))->toHaveCount(2);
|
||||
});
|
||||
@@ -291,6 +295,35 @@ describe('status updates after a server transfer', function () {
|
||||
expect($this->application->fresh()->status)->toStartWith('running');
|
||||
});
|
||||
|
||||
test('Sentinel push and status check match a 4.3 compose container deployed with a custom start command', function () {
|
||||
$containerName = 'public-api-'.$this->application->uuid.'-104512123456';
|
||||
$labels = [
|
||||
'coolify.managed' => 'true',
|
||||
'coolify.applicationId' => (string) $this->application->id,
|
||||
'coolify.pullRequestId' => '0',
|
||||
'coolify.name' => $containerName,
|
||||
// Without --project-name, the compose project is the deployment directory.
|
||||
'com.docker.compose.project' => 'r8wkc0gk0o4ws8gs4s8wwgs4',
|
||||
'com.docker.compose.service' => 'public-api',
|
||||
];
|
||||
|
||||
$job = new PushServerUpdateJob($this->server, ['containers' => [[
|
||||
'name' => $containerName, 'state' => 'running', 'health_status' => 'healthy', 'labels' => $labels,
|
||||
]]]);
|
||||
$job->handle();
|
||||
expect($this->application->fresh()->status)->toStartWith('running');
|
||||
|
||||
$this->application->update(['status' => 'exited']);
|
||||
$this->server->settings()->update(['is_reachable' => true, 'is_usable' => true]);
|
||||
GetContainersStatus::run($this->server->fresh(), collect([[
|
||||
'Name' => '/'.$containerName,
|
||||
'State' => ['Status' => 'running', 'Health' => ['Status' => 'healthy']],
|
||||
'RestartCount' => 0,
|
||||
'Config' => ['Labels' => $labels],
|
||||
]]), collect());
|
||||
expect($this->application->fresh()->status)->toStartWith('running');
|
||||
});
|
||||
|
||||
test('container status check matches an older application container by compose project', function () {
|
||||
$container = [
|
||||
'Name' => '/'.$this->application->uuid,
|
||||
|
||||
Reference in New Issue
Block a user