fix: preserve custom names and use Livewire redirects

Resolve custom container names consistently, clean up legacy container names during deployments, and route Livewire resource redirects through the navigation helper.
This commit is contained in:
Andras Bacsai 2026-08-28 09:46:57 +02:00
parent 1639941da7
commit 28fb694fcb
10 changed files with 144 additions and 20 deletions

View file

@ -261,14 +261,7 @@ public function __construct(public int $application_deployment_queue_id)
$this->configuration_dir = application_configuration_dir()."/{$this->application->uuid}";
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
if ($this->pull_request_id === 0) {
$this->container_name = $this->application->settings->custom_internal_name;
} else {
$this->container_name = addPreviewDeploymentSuffix($this->application->settings->custom_internal_name, $this->pull_request_id);
}
}
$this->container_name = $this->resolveContainerName();
$this->saved_outputs = collect();
@ -1986,6 +1979,19 @@ private function rolling_update()
}
}
private function resolveContainerName(): string
{
if (str($this->application->settings->custom_internal_name)->isEmpty()) {
return generateApplicationContainerName($this->application, $this->pull_request_id);
}
if ($this->pull_request_id === 0) {
return $this->application->settings->custom_internal_name;
}
return addPreviewDeploymentSuffix($this->application->settings->custom_internal_name, $this->pull_request_id);
}
private function health_check()
{
try {
@ -3428,6 +3434,9 @@ private function generate_compose_file()
$custom_compose = convertDockerRunToCompose($this->application->custom_docker_run_options);
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
if ($this->container_name !== $this->application->uuid) {
unset($docker_compose['services'][$this->container_name]);
}
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
@ -4027,7 +4036,10 @@ private function stop_running_container(bool $force = false)
$this->application_deployment_queue->addLogEntry('Removing old containers.');
if ($this->newVersionIsHealthy || $force) {
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->graceful_shutdown_container($this->container_name);
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
$this->containerNamesToRemove($containers)->each(function (string $containerName) {
$this->graceful_shutdown_container($containerName);
});
} else {
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($this->pull_request_id === 0) {
@ -4066,6 +4078,16 @@ private function stop_running_container(bool $force = false)
}
}
private function containerNamesToRemove(Collection $containers): Collection
{
return $containers
->pluck('Names')
->push($this->container_name)
->filter()
->unique()
->values();
}
private function start_by_compose_file()
{
try {

View file

@ -212,14 +212,14 @@ public function delete($password, $selectedActions = [])
if ($this->backup->database->getMorphClass() === ServiceDatabase::class) {
$serviceDatabase = $this->backup->database;
return redirect()->route('project.service.database.backups', [
return redirectRoute($this, 'project.service.database.backups', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
'service_uuid' => $serviceDatabase->service->uuid,
'stack_service_uuid' => $serviceDatabase->uuid,
]);
} else {
return redirect()->route('project.database.backup.index', [
return redirectRoute($this, 'project.database.backup.index', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
'database_uuid' => $this->parameters['database_uuid'],

View file

@ -428,7 +428,7 @@ public function deleteApplication($password, $selectedActions = [])
$this->serviceApplication->delete();
$this->dispatch('success', 'Application deleted.');
return redirect()->route('project.service.configuration', $this->parameters);
return redirectRoute($this, 'project.service.configuration', $this->parameters);
} catch (\Throwable $e) {
return handleError($e, $this);
}
@ -462,7 +462,7 @@ public function convertToDatabase()
$serviceApplication->delete();
});
return redirect()->route('project.service.configuration', $redirectParams);
return redirectRoute($this, 'project.service.configuration', $redirectParams);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View file

@ -169,9 +169,9 @@ public function delete()
$this->task->delete();
if ($this->type === 'application') {
return redirect()->route('project.application.scheduled-tasks.show', $this->parameters);
return redirectRoute($this, 'project.application.scheduled-tasks.show', $this->parameters);
} else {
return redirect()->route('project.service.scheduled-tasks.show', $this->parameters);
return redirectRoute($this, 'project.service.scheduled-tasks.show', $this->parameters);
}
} catch (\Exception $e) {
return handleError($e);

View file

@ -484,7 +484,7 @@ public function delete()
// @can and canGate checks against a deleted model (null team_id TypeError).
$this->github_app = null;
return redirect()->route('source.all');
return redirectRoute($this, 'source.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View file

@ -338,7 +338,7 @@ public function delete()
// @can and canGate checks against a deleted model (null team_id TypeError).
$this->gitlab_app = null;
return redirect()->route('source.all');
return redirectRoute($this, 'source.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View file

@ -43,7 +43,7 @@ public function delete()
$this->storage->delete();
return redirect()->route('storage.index');
return redirectRoute($this, 'storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View file

@ -0,0 +1,58 @@
<?php
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationSetting;
use Illuminate\Support\Collection;
function containerNamingJob(Application $application, int $pullRequestId = 0): array
{
$job = (new ReflectionClass(ApplicationDeploymentJob::class))->newInstanceWithoutConstructor();
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
$reflection->getProperty('application')->setValue($job, $application);
$reflection->getProperty('pull_request_id')->setValue($job, $pullRequestId);
return [$job, $reflection];
}
function applicationWithContainerNaming(string $customName = 'shadowuw'): Application
{
$application = new Application;
$application->forceFill(['uuid' => 'application-uuid']);
$application->setRelation('settings', new ApplicationSetting([
'custom_internal_name' => $customName,
'is_consistent_container_name_enabled' => true,
]));
return $application;
}
it('uses the custom container name when consistent naming is enabled', function () {
$application = applicationWithContainerNaming();
[$job, $reflection] = containerNamingJob($application);
expect($reflection->getMethod('resolveContainerName')->invoke($job))->toBe('shadowuw');
});
it('adds the pull request suffix to a custom container name', function () {
$application = applicationWithContainerNaming();
[$job, $reflection] = containerNamingJob($application, 42);
expect($reflection->getMethod('resolveContainerName')->invoke($job))->toBe('shadowuw-pr-42');
});
it('includes old generated containers when cleaning up a consistent deployment', function () {
$application = applicationWithContainerNaming();
[$job, $reflection] = containerNamingJob($application);
$reflection->getProperty('container_name')->setValue($job, 'shadowuw');
$containers = new Collection([
['Names' => 'application-uuid-192238854305'],
['Names' => 'shadowuw'],
]);
expect($reflection->getMethod('containerNamesToRemove')->invoke($job, $containers)->all())
->toBe(['application-uuid-192238854305', 'shadowuw']);
});

View file

@ -64,7 +64,7 @@ function generateComposeServiceWithCustomDockerOptions(string $customDockerOptio
'server' => $server,
'mainServer' => $server,
'pull_request_id' => 0,
'container_name' => $application->uuid,
'container_name' => 'custom-internal-name',
'production_image_name' => 'example/app:latest',
'deployment_uuid' => 'deployment-uuid',
'workdir' => '/artifacts/custom-docker-options-app',
@ -82,7 +82,8 @@ function generateComposeServiceWithCustomDockerOptions(string $customDockerOptio
it('applies an entrypoint when consistent naming and a custom internal name are configured', function () {
expect(generateComposeServiceWithCustomDockerOptions('--entrypoint "/bin/echo hello world"'))
->toHaveKey('entrypoint', '/bin/echo hello world');
->toHaveKey('entrypoint', '/bin/echo hello world')
->toHaveKey('container_name', 'custom-internal-name');
});
it('preserves custom network aliases when a static IP is configured', function () {

View file

@ -0,0 +1,43 @@
<?php
it('uses Livewire navigation after deleting or converting page resources', function (string $path, array $redirects) {
$contents = file_get_contents(dirname(__DIR__, 2).'/'.$path);
foreach ($redirects as $redirect) {
expect($contents)->toContain($redirect);
}
})->with([
'S3 storage' => [
'app/Livewire/Storage/Show.php',
["redirectRoute(\$this, 'storage.index')"],
],
'database backup schedule' => [
'app/Livewire/Project/Database/BackupEdit.php',
[
"redirectRoute(\$this, 'project.service.database.backups'",
"redirectRoute(\$this, 'project.database.backup.index'",
],
],
'scheduled task' => [
'app/Livewire/Project/Shared/ScheduledTask/Show.php',
[
"redirectRoute(\$this, 'project.application.scheduled-tasks.show'",
"redirectRoute(\$this, 'project.service.scheduled-tasks.show'",
],
],
'GitHub source' => [
'app/Livewire/Source/Github/Change.php',
["redirectRoute(\$this, 'source.all')"],
],
'GitLab source' => [
'app/Livewire/Source/Gitlab/Change.php',
["redirectRoute(\$this, 'source.all')"],
],
'service resources' => [
'app/Livewire/Project/Service/Index.php',
[
"return redirectRoute(\$this, 'project.service.configuration', \$this->parameters);",
"return redirectRoute(\$this, 'project.service.configuration', \$redirectParams);",
],
],
]);