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
+31 -9
View File
@@ -261,14 +261,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$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 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
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 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$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 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$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 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
}
private function containerNamesToRemove(Collection $containers): Collection
{
return $containers
->pluck('Names')
->push($this->container_name)
->filter()
->unique()
->values();
}
private function start_by_compose_file()
{
try {
+2 -2
View File
@@ -212,14 +212,14 @@ class BackupEdit extends Component
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'],
+2 -2
View File
@@ -428,7 +428,7 @@ class Index extends Component
$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 @@ class Index extends Component
$serviceApplication->delete();
});
return redirect()->route('project.service.configuration', $redirectParams);
return redirectRoute($this, 'project.service.configuration', $redirectParams);
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -169,9 +169,9 @@ class Show extends Component
$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);
+1 -1
View File
@@ -484,7 +484,7 @@ class Change extends Component
// @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);
}
+1 -1
View File
@@ -338,7 +338,7 @@ class Change extends Component
// @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);
}
+1 -1
View File
@@ -43,7 +43,7 @@ class Show extends Component
$this->storage->delete();
return redirect()->route('storage.index');
return redirectRoute($this, 'storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -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']);
});
@@ -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 () {
@@ -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);",
],
],
]);