diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 35d83ee6f..ddfd45531 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -358,6 +358,19 @@ function parseDockerVolumeString(string $volumeString): array ]; } +function addTraefikDockerNetworkLabel(Collection $labels, string $network): Collection +{ + $hasUserDefinedNetwork = $labels->contains( + fn ($label): bool => is_string($label) && str($label)->before('=')->is('traefik.docker.network') + ); + + if (! $hasUserDefinedNetwork) { + $labels->push("traefik.docker.network={$network}"); + } + + return $labels; +} + function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null, ?string $commit = null): Collection { $uuid = data_get($resource, 'uuid'); @@ -1346,6 +1359,9 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int $redirectDirection = in_array($composeRedirect, ['www', 'non-www', 'both'], true) ? $composeRedirect : 'both'; + if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) { + $serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first()); + } if ($shouldGenerateLabelsExactly) { switch ($server->proxyType()) { case ProxyTypes::TRAEFIK->value: @@ -2620,6 +2636,9 @@ function serviceParser(Service $resource): Collection $redirectDirection = in_array(data_get($originalResource, 'redirect'), ['www', 'non-www', 'both'], true) ? data_get($originalResource, 'redirect') : 'both'; + if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) { + $serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first()); + } if ($shouldGenerateLabelsExactly) { switch ($server->proxyType()) { case ProxyTypes::TRAEFIK->value: diff --git a/tests/Feature/ApplicationParserDockerComposeDomainsTest.php b/tests/Feature/ApplicationParserDockerComposeDomainsTest.php index 539dba5f2..270927ca1 100644 --- a/tests/Feature/ApplicationParserDockerComposeDomainsTest.php +++ b/tests/Feature/ApplicationParserDockerComposeDomainsTest.php @@ -398,3 +398,59 @@ YAML; expect(json_decode($plainApplication->docker_compose_domains, true))->toBeNull(); }); + +test('applicationParser selects the Coolify network for Traefik routed compose services', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => StandaloneDocker::class, + 'build_pack' => 'dockercompose', + 'docker_compose_raw' => <<<'YAML' +services: + frontend: + image: nginx:latest + networks: + - custom-network +networks: + custom-network: {} +YAML, + 'docker_compose_domains' => json_encode([ + 'frontend' => ['domain' => 'https://example.com'], + ]), + ]); + + $parsedCompose = applicationParser($application); + $labels = collect(data_get($parsedCompose, 'services.frontend.labels')); + + expect($labels->values()->all())->toContain("traefik.docker.network={$application->uuid}"); +}); + +test('applicationParser preserves a user-selected Traefik network', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => StandaloneDocker::class, + 'build_pack' => 'dockercompose', + 'docker_compose_raw' => <<<'YAML' +services: + frontend: + image: nginx:latest + labels: + traefik.docker.network: custom-network + networks: + - custom-network +networks: + custom-network: {} +YAML, + 'docker_compose_domains' => json_encode([ + 'frontend' => ['domain' => 'https://example.com'], + ]), + ]); + + $parsedCompose = applicationParser($application); + $labels = collect(data_get($parsedCompose, 'services.frontend.labels')); + + expect($labels->values()->all()) + ->toContain('traefik.docker.network=custom-network') + ->not->toContain("traefik.docker.network={$application->uuid}"); +}); diff --git a/tests/Feature/TraefikServiceDockerNetworkLabelTest.php b/tests/Feature/TraefikServiceDockerNetworkLabelTest.php new file mode 100644 index 000000000..4cf4ebc56 --- /dev/null +++ b/tests/Feature/TraefikServiceDockerNetworkLabelTest.php @@ -0,0 +1,49 @@ +create(); + $server = Server::factory()->create(['team_id' => $team->id]); + $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail(); + $project = Project::factory()->create(['team_id' => $team->id]); + $environment = Environment::factory()->create(['project_id' => $project->id]); + $service = Service::factory()->create([ + 'environment_id' => $environment->id, + 'server_id' => $server->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + 'docker_compose_raw' => <<<'YAML' +services: + app: + image: nginx:latest + networks: + - custom-network +networks: + custom-network: {} +YAML, + ]); + + ServiceApplication::create([ + 'name' => 'app', + 'service_id' => $service->id, + 'fqdn' => 'https://example.com', + ]); + + $parsedCompose = serviceParser($service); + $labels = collect(data_get($parsedCompose, 'services.app.labels')); + + expect($labels->values()->all())->toContain("traefik.docker.network={$service->uuid}"); +}); diff --git a/tests/Unit/TraefikDockerNetworkLabelTest.php b/tests/Unit/TraefikDockerNetworkLabelTest.php new file mode 100644 index 000000000..e13acb113 --- /dev/null +++ b/tests/Unit/TraefikDockerNetworkLabelTest.php @@ -0,0 +1,30 @@ +values()->all())->toContain('traefik.docker.network=app-uuid'); +}); + +it('preserves a user-selected Traefik network', function () { + $labels = addTraefikDockerNetworkLabel(collect([ + 'traefik.enable=true', + 'traefik.docker.network=custom-network', + ]), 'app-uuid'); + + expect($labels->values()->all()) + ->toContain('traefik.docker.network=custom-network') + ->not->toContain('traefik.docker.network=app-uuid'); +}); + +it('treats a bare user-provided Traefik network label as authoritative', function () { + $labels = addTraefikDockerNetworkLabel(collect([ + 'traefik.docker.network', + ]), 'app-uuid'); + + expect($labels->values()->all()) + ->toContain('traefik.docker.network') + ->not->toContain('traefik.docker.network=app-uuid'); +});