mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 17:44:36 +08:00
fix(api): reject custom_internal_name without consistent container naming
Container names use custom_internal_name only when consistent container naming is on. The API accepted the name and then ignored it. - Return 422 with a message that says to set is_consistent_container_name_enabled to true - Accept the name when the request or the application turns the setting on - Document the requirement in the OpenAPI spec
This commit is contained in:
1 parent
ac5a0c289f
commit
50268139be
5 files changed
+171
-18
No files matched your search
@@ -183,6 +183,26 @@ class ApplicationsController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
/**
|
||||
* Container names honor custom_internal_name only with consistent container naming, so reject a name that would be ignored.
|
||||
*/
|
||||
private function customInternalNameValidationResponse(array $settings, ?Application $application = null): ?JsonResponse
|
||||
{
|
||||
if (! filled($settings['custom_internal_name'] ?? null)) {
|
||||
return null;
|
||||
}
|
||||
$consistentNaming = $settings['is_consistent_container_name_enabled']
|
||||
?? (bool) $application?->settings?->is_consistent_container_name_enabled;
|
||||
if ($consistentNaming) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['custom_internal_name' => ['Set is_consistent_container_name_enabled to true to use custom_internal_name. Coolify ignores the custom internal name while consistent container naming is turned off.']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
private function applyApplicationSettings(Application $application, array $settings): void
|
||||
{
|
||||
if ($settings === []) {
|
||||
@@ -419,7 +439,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -614,7 +634,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -809,7 +829,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -975,7 +995,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -1137,7 +1157,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -1391,6 +1411,9 @@ class ApplicationsController extends Controller
|
||||
if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $destination->server)) {
|
||||
return $prefixValidation;
|
||||
}
|
||||
if ($internalNameValidation = $this->customInternalNameValidationResponse($applicationSettings)) {
|
||||
return $internalNameValidation;
|
||||
}
|
||||
if ($type === 'public') {
|
||||
$validationRules = [
|
||||
'git_repository' => ['string', 'required', new ValidGitRepositoryUrl],
|
||||
@@ -3128,7 +3151,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_device_ids' => ['type' => 'string', 'nullable' => true, 'description' => 'Comma-separated GPU device IDs.'],
|
||||
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
|
||||
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
|
||||
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
|
||||
@@ -3312,6 +3335,9 @@ class ApplicationsController extends Controller
|
||||
if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $application->destination->server, $application)) {
|
||||
return $prefixValidation;
|
||||
}
|
||||
if ($internalNameValidation = $this->customInternalNameValidationResponse($applicationSettings, $application)) {
|
||||
return $internalNameValidation;
|
||||
}
|
||||
$requestedBuildPack = $request->input('build_pack', $application->build_pack);
|
||||
if (($applicationSettings['is_raw_compose_deployment_enabled'] ?? false) && $requestedBuildPack !== 'dockercompose') {
|
||||
return response()->json([
|
||||
|
||||
+6
-6
@@ -949,7 +949,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
@@ -1540,7 +1540,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
@@ -2131,7 +2131,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
@@ -2617,7 +2617,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
@@ -3083,7 +3083,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
@@ -3794,7 +3794,7 @@
|
||||
"custom_internal_name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Custom internal container name."
|
||||
"description": "Custom internal container name. Requires is_consistent_container_name_enabled to be true."
|
||||
},
|
||||
"custom_container_name_prefix": {
|
||||
"type": "string",
|
||||
|
||||
+6
-6
@@ -622,7 +622,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1008,7 +1008,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1394,7 +1394,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1721,7 +1721,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -2034,7 +2034,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -2501,7 +2501,7 @@ paths:
|
||||
custom_internal_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 'Custom internal container name.'
|
||||
description: 'Custom internal container name. Requires is_consistent_container_name_enabled to be true.'
|
||||
custom_container_name_prefix:
|
||||
type: string
|
||||
nullable: true
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
config(['app.maintenance.store' => 'array']);
|
||||
InstanceSettings::unguarded(fn () => 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]);
|
||||
|
||||
$this->bearerToken = $this->user->createToken('custom-internal-name-test', ['*'])->plainTextToken;
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
$this->application = Application::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
});
|
||||
|
||||
function customInternalNameCreatePayload(array $overrides): array
|
||||
{
|
||||
return array_merge([
|
||||
'project_uuid' => test()->project->uuid,
|
||||
'environment_uuid' => test()->environment->uuid,
|
||||
'server_uuid' => test()->server->uuid,
|
||||
'git_repository' => 'https://gitlab.com/coolify/custom-internal-name',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'autogenerate_domain' => false,
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
const CUSTOM_INTERNAL_NAME_NEEDS_CONSISTENT_NAMING = 'Set is_consistent_container_name_enabled to true to use custom_internal_name. Coolify ignores the custom internal name while consistent container naming is turned off.';
|
||||
|
||||
test('updating only custom_internal_name is rejected while consistent container naming is off', function () {
|
||||
$this->withToken($this->bearerToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", ['custom_internal_name' => 'my-app'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('errors.custom_internal_name.0', CUSTOM_INTERNAL_NAME_NEEDS_CONSISTENT_NAMING);
|
||||
|
||||
expect($this->application->fresh()->settings->custom_internal_name)->toBeNull();
|
||||
});
|
||||
|
||||
test('updating custom_internal_name while turning consistent container naming off is rejected', function () {
|
||||
$this->application->settings->update(['is_consistent_container_name_enabled' => true]);
|
||||
|
||||
$this->withToken($this->bearerToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
'custom_internal_name' => 'my-app',
|
||||
'is_consistent_container_name_enabled' => false,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('errors.custom_internal_name.0', CUSTOM_INTERNAL_NAME_NEEDS_CONSISTENT_NAMING);
|
||||
});
|
||||
|
||||
test('updating custom_internal_name together with consistent container naming succeeds', function () {
|
||||
$this->withToken($this->bearerToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
'custom_internal_name' => 'my-app',
|
||||
'is_consistent_container_name_enabled' => true,
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$settings = $this->application->fresh()->settings;
|
||||
expect($settings->custom_internal_name)->toBe('my-app')
|
||||
->and($settings->is_consistent_container_name_enabled)->toBeTrue();
|
||||
});
|
||||
|
||||
test('updating only custom_internal_name succeeds when consistent container naming is already on', function () {
|
||||
$this->application->settings->update(['is_consistent_container_name_enabled' => true]);
|
||||
|
||||
$this->withToken($this->bearerToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", ['custom_internal_name' => 'my-app'])
|
||||
->assertOk();
|
||||
|
||||
expect($this->application->fresh()->settings->custom_internal_name)->toBe('my-app');
|
||||
});
|
||||
|
||||
test('clearing custom_internal_name does not need consistent container naming', function (?string $value) {
|
||||
$this->withToken($this->bearerToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", ['custom_internal_name' => $value])
|
||||
->assertOk();
|
||||
})->with(['null' => [null], 'empty string' => ['']]);
|
||||
|
||||
test('creating an application with custom_internal_name requires consistent container naming', function () {
|
||||
Queue::fake();
|
||||
|
||||
$this->withToken($this->bearerToken)
|
||||
->postJson('/api/v1/applications/public', customInternalNameCreatePayload(['custom_internal_name' => 'my-app']))
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('errors.custom_internal_name.0', CUSTOM_INTERNAL_NAME_NEEDS_CONSISTENT_NAMING);
|
||||
|
||||
expect(Application::query()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('creating an application with custom_internal_name and consistent container naming succeeds', function () {
|
||||
Queue::fake();
|
||||
|
||||
$response = $this->withToken($this->bearerToken)
|
||||
->postJson('/api/v1/applications/public', customInternalNameCreatePayload([
|
||||
'custom_internal_name' => 'my-app',
|
||||
'is_consistent_container_name_enabled' => true,
|
||||
]))
|
||||
->assertCreated();
|
||||
|
||||
expect(Application::query()->where('uuid', $response->json('uuid'))->sole()->settings->custom_internal_name)->toBe('my-app');
|
||||
});
|
||||
@@ -351,6 +351,7 @@ test('PATCH /api/v1/applications/{uuid} accepts Docker-compatible custom interna
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
'custom_internal_name' => $name,
|
||||
'is_consistent_container_name_enabled' => true,
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
|
||||
Reference in new issue
Block a user