mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-25 17:01:49 -04:00
feat: add custom container name prefix option (#11704)
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ApplicationSetting;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\LocalFileVolume;
|
||||
@@ -61,6 +62,7 @@ class ApplicationsController extends Controller
|
||||
'gpu_options',
|
||||
'is_consistent_container_name_enabled',
|
||||
'custom_internal_name',
|
||||
'custom_container_name_prefix',
|
||||
];
|
||||
|
||||
private const BOOLEAN_APPLICATION_SETTING_FIELDS = [
|
||||
@@ -153,9 +155,26 @@ class ApplicationsController extends Controller
|
||||
: $request->input($field);
|
||||
}
|
||||
|
||||
if (array_key_exists('custom_container_name_prefix', $settings)) {
|
||||
$settings['custom_container_name_prefix'] = str($settings['custom_container_name_prefix'])->slug()->value() ?: null;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function containerNamePrefixValidationResponse(array $settings, Server $server, ?Application $application = null): ?JsonResponse
|
||||
{
|
||||
$prefix = $settings['custom_container_name_prefix'] ?? null;
|
||||
if (! filled($prefix) || ! ApplicationSetting::isContainerNamePrefixInUse($prefix, $server, $application?->id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['custom_container_name_prefix' => ['This container name prefix is already in use by another application.']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
private function applyApplicationSettings(Application $application, array $settings): void
|
||||
{
|
||||
if ($settings === []) {
|
||||
@@ -393,6 +412,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
@@ -587,6 +607,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
@@ -781,6 +802,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
@@ -946,6 +968,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
@@ -1107,6 +1130,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
@@ -1335,6 +1359,9 @@ class ApplicationsController extends Controller
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $destination->server)) {
|
||||
return $prefixValidation;
|
||||
}
|
||||
if ($type === 'public') {
|
||||
$validationRules = [
|
||||
'git_repository' => ['string', 'required', new ValidGitRepositoryUrl],
|
||||
@@ -2967,6 +2994,7 @@ class ApplicationsController extends Controller
|
||||
'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_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.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
@@ -3140,6 +3168,9 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
|
||||
$applicationSettings = $this->applicationSettingsFromRequest($request);
|
||||
if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $application->destination->server, $application)) {
|
||||
return $prefixValidation;
|
||||
}
|
||||
$requestedBuildPack = $request->input('build_pack', $application->build_pack);
|
||||
if (($applicationSettings['is_raw_compose_deployment_enabled'] ?? false) && $requestedBuildPack !== 'dockercompose') {
|
||||
return response()->json([
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationSetting;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -69,6 +70,9 @@ class Advanced extends Component
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $customInternalName = null;
|
||||
|
||||
#[Validate(['string', 'nullable', 'max:'.ApplicationSetting::MAX_CONTAINER_NAME_PREFIX_LENGTH])]
|
||||
public ?string $customContainerNamePrefix = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isGzipEnabled = true;
|
||||
|
||||
@@ -111,6 +115,7 @@ class Advanced extends Component
|
||||
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
|
||||
$this->application->settings->is_consistent_container_name_enabled = $this->isConsistentContainerNameEnabled;
|
||||
$this->application->settings->custom_internal_name = $this->customInternalName;
|
||||
$this->application->settings->custom_container_name_prefix = $this->customContainerNamePrefix;
|
||||
$this->application->settings->is_gzip_enabled = $this->isGzipEnabled;
|
||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||
@@ -137,6 +142,7 @@ class Advanced extends Component
|
||||
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
|
||||
$this->isConsistentContainerNameEnabled = $this->application->settings->is_consistent_container_name_enabled;
|
||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||
$this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
|
||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
@@ -258,6 +264,28 @@ class Advanced extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function saveCustomNamePrefix()
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
$this->customContainerNamePrefix = str($this->customContainerNamePrefix)->slug()->value() ?: null;
|
||||
|
||||
if ($this->customContainerNamePrefix && ApplicationSetting::isContainerNamePrefixInUse($this->customContainerNamePrefix, $this->application->destination->server, $this->application->id)) {
|
||||
$this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
|
||||
$this->dispatch('error', 'This container name prefix is already in use by another application on this Coolify instance.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'Container name prefix saved.');
|
||||
$this->dispatch('configurationChanged');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveStopGracePeriod()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -32,6 +32,7 @@ use OpenApi\Attributes as OA;
|
||||
'is_stripprefix_enabled' => ['type' => 'boolean'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean'],
|
||||
'custom_internal_name' => ['type' => 'string', 'nullable' => true],
|
||||
'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean'],
|
||||
'is_container_label_readonly_enabled' => ['type' => 'boolean'],
|
||||
@@ -49,6 +50,12 @@ use OpenApi\Attributes as OA;
|
||||
)]
|
||||
class ApplicationSetting extends Model
|
||||
{
|
||||
/**
|
||||
* Keeps generated names (prefix, timestamp and for compose apps the service name) well below the
|
||||
* 63 character DNS label limit, with room for a longer suffix in the future.
|
||||
*/
|
||||
public const MAX_CONTAINER_NAME_PREFIX_LENGTH = 30;
|
||||
|
||||
protected $casts = [
|
||||
'is_static' => 'boolean',
|
||||
'is_spa' => 'boolean',
|
||||
@@ -106,6 +113,7 @@ class ApplicationSetting extends Model
|
||||
'is_stripprefix_enabled',
|
||||
'connect_to_docker_network',
|
||||
'custom_internal_name',
|
||||
'custom_container_name_prefix',
|
||||
'is_container_label_escape_enabled',
|
||||
'is_env_sorting_enabled',
|
||||
'is_container_label_readonly_enabled',
|
||||
@@ -121,6 +129,18 @@ class ApplicationSetting extends Model
|
||||
'stop_grace_period',
|
||||
];
|
||||
|
||||
/**
|
||||
* Like custom container names, a prefix must be unique per server so that uuid, custom container
|
||||
* name and prefix each identify one container when resolving connections.
|
||||
*/
|
||||
public static function isContainerNamePrefixInUse(string $prefix, Server $server, ?int $ignoreApplicationId = null): bool
|
||||
{
|
||||
return $server->applications()->contains(function (Application $application) use ($prefix, $ignoreApplicationId) {
|
||||
return $application->id !== $ignoreApplicationId
|
||||
&& in_array($prefix, [$application->uuid, $application->settings->custom_container_name_prefix, $application->settings->custom_internal_name], true);
|
||||
});
|
||||
}
|
||||
|
||||
public function stopGracePeriodSeconds(): int
|
||||
{
|
||||
if (
|
||||
|
||||
@@ -170,6 +170,7 @@ class ApplicationConfigurationSnapshot
|
||||
$this->item('custom_network_aliases', 'Network aliases', $this->application->custom_network_aliases, 'redeploy'),
|
||||
$this->item('connect_to_docker_network', 'Connect to Docker network', data_get($this->application, 'settings.connect_to_docker_network'), 'redeploy'),
|
||||
$this->item('custom_internal_name', 'Custom container name', data_get($this->application, 'settings.custom_internal_name'), 'redeploy'),
|
||||
$this->item('custom_container_name_prefix', 'Container name prefix', data_get($this->application, 'settings.custom_container_name_prefix'), 'redeploy'),
|
||||
$this->item('is_consistent_container_name_enabled', 'Consistent container name', data_get($this->application, 'settings.is_consistent_container_name_enabled'), 'redeploy'),
|
||||
$this->item('is_container_label_escape_enabled', 'Escape container labels', data_get($this->application, 'settings.is_container_label_escape_enabled'), 'redeploy'),
|
||||
$this->item('is_container_label_readonly_enabled', 'Read-only container labels', data_get($this->application, 'settings.is_container_label_readonly_enabled'), 'redeploy'),
|
||||
|
||||
@@ -4,6 +4,7 @@ use App\Actions\Shared\MigrateResourceToDestination;
|
||||
use App\Enums\BuildPackTypes;
|
||||
use App\Enums\RedirectTypes;
|
||||
use App\Enums\StaticImageTypes;
|
||||
use App\Models\ApplicationSetting;
|
||||
use App\Models\Environment;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
@@ -141,6 +142,7 @@ function sharedDataApplications()
|
||||
'gpu_options' => 'string|nullable',
|
||||
'is_consistent_container_name_enabled' => 'boolean',
|
||||
'custom_internal_name' => 'string|nullable',
|
||||
'custom_container_name_prefix' => 'string|nullable|max:'.ApplicationSetting::MAX_CONTAINER_NAME_PREFIX_LENGTH,
|
||||
'preview_url_template' => 'string',
|
||||
'max_restart_count' => 'integer|min:0',
|
||||
'stop_grace_period' => 'nullable|integer|min:'.MIN_STOP_GRACE_PERIOD_SECONDS.'|max:'.MAX_STOP_GRACE_PERIOD_SECONDS,
|
||||
@@ -408,6 +410,7 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
|
||||
$request->offsetUnset('gpu_options');
|
||||
$request->offsetUnset('is_consistent_container_name_enabled');
|
||||
$request->offsetUnset('custom_internal_name');
|
||||
$request->offsetUnset('custom_container_name_prefix');
|
||||
$request->offsetUnset('docker_compose_raw');
|
||||
$request->offsetUnset('tags');
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ function generateApplicationContainerName(Application $application, $pull_reques
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $application->uuid.'-'.$now;
|
||||
return ($application->settings->custom_container_name_prefix ?: $application->uuid).'-'.$now;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->string('custom_container_name_prefix')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -45,10 +45,21 @@
|
||||
['value' => true, 'label' => 'Consistent name (no rolling updates)'],
|
||||
]" :disabled="! $canUpdate" />
|
||||
@if ($isConsistentContainerNameEnabled === true)
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name is saved automatically and converted to slug format. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
id="customInternalName" label="Custom container name" canGate="update"
|
||||
wire:change="saveCustomName" :canResource="$application" />
|
||||
<form wire:submit="saveCustomName" class="w-full">
|
||||
<x-unsaved-bar action="saveCustomName" targets="customInternalName" />
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name is converted to slug format when saved. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
id="customInternalName" label="Custom container name" canGate="update"
|
||||
:canResource="$application" />
|
||||
</form>
|
||||
@else
|
||||
<form wire:submit="saveCustomNamePrefix" class="w-full">
|
||||
<x-unsaved-bar action="saveCustomNamePrefix" targets="customContainerNamePrefix" />
|
||||
<x-forms.input
|
||||
helper="Optional prefix for generated container names. Containers are named <span class='font-bold'>prefix-timestamp</span>, for example <span class='font-bold'>shop-api-20260908T141530</span>, instead of starting with <span class='font-bold'>{{ $application->uuid }}</span>.<br><br>The prefix is converted to slug format when saved, can be up to {{ \App\Models\ApplicationSetting::MAX_CONTAINER_NAME_PREFIX_LENGTH }} characters and must be unique on this server. Rolling updates keep working."
|
||||
id="customContainerNamePrefix" label="Container name prefix" placeholder="e.g. my-api"
|
||||
canGate="update" :canResource="$application" />
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</x-application.settings-section>
|
||||
|
||||
@@ -467,3 +467,32 @@ test('rejects swarm fields on application update', function (string $field, mixe
|
||||
'swarm_placement_constraints' => ['swarm_placement_constraints', 'node.role==worker'],
|
||||
'is_swarm_only_worker_nodes' => ['is_swarm_only_worker_nodes', true],
|
||||
]);
|
||||
|
||||
test('PATCH /api/v1/applications/{uuid} saves a slugged container name prefix', function () {
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", ['custom_container_name_prefix' => 'My API'])
|
||||
->assertOk();
|
||||
|
||||
expect($this->application->fresh()->settings->custom_container_name_prefix)->toBe('my-api');
|
||||
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->getJson("/api/v1/applications/{$this->application->uuid}")
|
||||
->assertOk()
|
||||
->assertJsonPath('settings.custom_container_name_prefix', 'my-api');
|
||||
});
|
||||
|
||||
test('PATCH /api/v1/applications/{uuid} rejects a container name prefix that is in use', function () {
|
||||
$otherApplication = Application::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
$otherApplication->settings->update(['custom_container_name_prefix' => 'shared-prefix']);
|
||||
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", ['custom_container_name_prefix' => 'shared-prefix'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('custom_container_name_prefix');
|
||||
|
||||
expect($this->application->fresh()->settings->custom_container_name_prefix)->toBeNull();
|
||||
});
|
||||
|
||||
@@ -247,6 +247,18 @@ test('member cannot submit application advanced settings', function () {
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot save the application container name prefix', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationAdvanced::class, ['application' => $this->application])
|
||||
->set('customContainerNamePrefix', 'member-prefix')
|
||||
->call('saveCustomNamePrefix')
|
||||
->assertDispatched('error');
|
||||
|
||||
expect($this->application->settings->fresh()->custom_container_name_prefix)->toBeNull();
|
||||
});
|
||||
|
||||
test('the private application advanced syncData helper is not remotely callable', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
@@ -104,3 +104,47 @@ it('only shows the custom container name for consistent naming', function () {
|
||||
->set('isConsistentContainerNameEnabled', true)
|
||||
->assertSee('Custom container name');
|
||||
});
|
||||
|
||||
it('saves a slugged container name prefix in generated naming mode', function () {
|
||||
$otherTeamApplication = createApplicationForContainerNamingTest();
|
||||
$otherTeamApplication->settings->update(['custom_container_name_prefix' => 'my-api']);
|
||||
|
||||
$application = createApplicationForContainerNamingTest();
|
||||
$application->settings->update(['custom_internal_name' => 'legacy-name']);
|
||||
$application = $application->fresh(['environment.project', 'settings', 'destination']);
|
||||
|
||||
Livewire::test(Advanced::class, ['application' => $application])
|
||||
->assertSee('Container name prefix')
|
||||
->set('customContainerNamePrefix', 'My API')
|
||||
->call('saveCustomNamePrefix')
|
||||
->assertDispatched('success')
|
||||
->assertSet('customContainerNamePrefix', 'my-api');
|
||||
|
||||
$settings = $application->settings()->first();
|
||||
expect($settings->custom_container_name_prefix)->toBe('my-api')
|
||||
->and($settings->custom_internal_name)->toBe('legacy-name');
|
||||
});
|
||||
|
||||
it('rejects a container name prefix already used on the server', function () {
|
||||
$application = createApplicationForContainerNamingTest();
|
||||
$sibling = fn () => Application::factory()->create([
|
||||
'environment_id' => $application->environment_id,
|
||||
'destination_id' => $application->destination_id,
|
||||
'destination_type' => $application->destination_type,
|
||||
]);
|
||||
$prefixedApplication = $sibling();
|
||||
$prefixedApplication->settings->update(['custom_container_name_prefix' => 'shared-prefix']);
|
||||
$sibling()->settings->update(['custom_internal_name' => 'api']);
|
||||
|
||||
$application = $application->fresh(['environment.project', 'settings', 'destination']);
|
||||
$component = Livewire::test(Advanced::class, ['application' => $application]);
|
||||
|
||||
foreach (['shared-prefix', 'api', $prefixedApplication->uuid] as $takenPrefix) {
|
||||
$component->set('customContainerNamePrefix', $takenPrefix)
|
||||
->call('saveCustomNamePrefix')
|
||||
->assertDispatched('error')
|
||||
->assertSet('customContainerNamePrefix', null);
|
||||
}
|
||||
|
||||
expect($application->settings()->first()->custom_container_name_prefix)->toBeNull();
|
||||
});
|
||||
|
||||
@@ -64,3 +64,22 @@ it('recognises generated container names in both timestamp formats', function ()
|
||||
->and(isGeneratedContainerName('application-uuid-pr-42'))->toBeFalse()
|
||||
->and(isGeneratedContainerName('my-api'))->toBeFalse();
|
||||
});
|
||||
|
||||
function applicationWithContainerNamePrefix(string $prefix = 'my-api', bool $consistent = false): Application
|
||||
{
|
||||
$application = new Application;
|
||||
$application->forceFill(['uuid' => 'application-uuid']);
|
||||
$application->setRelation('settings', new ApplicationSetting([
|
||||
'custom_container_name_prefix' => $prefix,
|
||||
'is_consistent_container_name_enabled' => $consistent,
|
||||
]));
|
||||
|
||||
return $application;
|
||||
}
|
||||
|
||||
it('uses the container name prefix for generated container names only', function () {
|
||||
expect(generateApplicationContainerName(applicationWithContainerNamePrefix()))->toMatch('/^my-api-\d{8}T\d{6}$/')
|
||||
->and(generateApplicationContainerName(applicationWithContainerNamePrefix('')))->toMatch('/^application-uuid-\d{8}T\d{6}$/')
|
||||
->and(generateApplicationContainerName(applicationWithContainerNamePrefix(consistent: true)))->toBe('application-uuid')
|
||||
->and(generateApplicationContainerName(applicationWithContainerNamePrefix(), 42))->toBe('application-uuid-pr-42');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user