From bafbc2e3d3ea6ec15ea51981517e9c17118235f7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:28:18 +0100 Subject: [PATCH] fix(api): regenerate proxy labels when exposed ports change (#12334) --- .../Api/ApplicationsController.php | 2 +- .../ApplicationPortsExposesLabelsApiTest.php | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Api/ApplicationPortsExposesLabelsApiTest.php diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 5915724f1e..608e88a4c2 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -3818,7 +3818,7 @@ class ApplicationsController extends Controller // Must run after fqdn is filled: flags are kept only for domains the app still has. $application->setNoindexDomains($request->input('noindex_domains') ?? []); } - if ($application->settings->is_container_label_readonly_enabled && ($requestHasDomains || $requestHasNoindexDomains || $requestHasHttpBasicAuth) && $server->isProxyShouldRun()) { + if ($application->settings->is_container_label_readonly_enabled && ($requestHasDomains || $requestHasNoindexDomains || $requestHasHttpBasicAuth || $application->isDirty('ports_exposes')) && $server->isProxyShouldRun()) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); } $application->withoutAuditLogging(fn () => $application->save()); diff --git a/tests/Feature/Api/ApplicationPortsExposesLabelsApiTest.php b/tests/Feature/Api/ApplicationPortsExposesLabelsApiTest.php new file mode 100644 index 0000000000..305861a7d3 --- /dev/null +++ b/tests/Feature/Api/ApplicationPortsExposesLabelsApiTest.php @@ -0,0 +1,78 @@ + InstanceSettings::firstOrCreate(['id' => 0])); + + $this->team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + session(['currentTeam' => $this->team]); + + $plainTextToken = Str::random(40); + $token = $this->user->tokens()->create([ + 'name' => 'ports-exposes-api-test-'.Str::random(6), + 'token' => hash('sha256', $plainTextToken), + 'abilities' => ['*'], + 'team_id' => $this->team->id, + ]); + $this->bearerToken = $token->getKey().'|'.$plainTextToken; + + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $destination = StandaloneDocker::where('server_id', $this->server->id)->first(); + $environment = Environment::factory()->create([ + 'project_id' => Project::factory()->create(['team_id' => $this->team->id])->id, + ]); + $this->application = Application::factory()->create([ + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + 'fqdn' => 'https://app.example.com', + 'ports_exposes' => '80', + ]); + $this->application->settings->update(['is_container_label_readonly_enabled' => true]); + $this->application->update([ + 'custom_labels' => base64_encode(implode("\n", generateLabelsApplication($this->application->fresh()))), + ]); +}); + +function patchPortsExposes(string $bearerToken, string $uuid, array $payload) +{ + return test()->withHeaders(['Authorization' => 'Bearer '.$bearerToken]) + ->patchJson("/api/v1/applications/{$uuid}", $payload); +} + +test('regenerates the load balancer port label when ports_exposes changes', function () { + expect((string) $this->application->fresh()->parseContainerLabels()) + ->toContain('loadbalancer.server.port=80'); + + patchPortsExposes($this->bearerToken, $this->application->uuid, ['ports_exposes' => '9000']) + ->assertOk(); + + $labels = (string) $this->application->fresh()->parseContainerLabels(); + + expect($labels)->toContain('loadbalancer.server.port=9000') + ->not->toContain('loadbalancer.server.port=80'); +}); + +test('keeps custom labels when readonly labels are disabled', function () { + $this->application->settings->update(['is_container_label_readonly_enabled' => false]); + $this->application->update(['custom_labels' => base64_encode('my.custom=label')]); + + patchPortsExposes($this->bearerToken, $this->application->uuid, ['ports_exposes' => '9000']) + ->assertOk(); + + expect((string) $this->application->fresh()->parseContainerLabels())->toBe('my.custom=label'); +});