fix(migration): block server migration when the target proxy is not running

This commit is contained in:
Andras Bacsai
2026-10-24 17:55:11 +02:00
parent e4c1e1a6b3
commit 8aed790a78
6 changed files with 114 additions and 4 deletions
@@ -171,7 +171,9 @@ describe('after the session team changes', function () {
test('migrateTo rejects a destination of the session team', function () {
resourceOperationsAfterTeamSwitch($this, $this->applicationA)
->call('migrateTo', $this->destinationB->uuid)
->assertHasErrors(['destination_id' => 'Destination not found.']);
->assertDispatched('error', 'Destination not found.');
expect($this->applicationA->fresh()->destination_id)->toBe($this->destinationA->id);
});
test('cloneTo and moveTo still work inside the resource team', function () {
+15
View File
@@ -145,6 +145,21 @@ it('seeds one predefined root qemu server', function () {
->and(Server::query()->where('uuid', 'like', 'development-qemu-%')->count())->toBe(1);
});
it('seeds a new qemu server with the caddy proxy', function () {
$server = SeedDevelopmentQemuServer::run('debian-root');
expect($server->proxyType())->toBe('CADDY')
->and($server->proxy->get('status'))->toBe('exited');
});
it('keeps the proxy of an existing server when it seeds a qemu vm for it', function () {
$this->seed(ServerSeeder::class);
$server = SeedDevelopmentQemuServer::run('ubuntu-root', true, true);
expect($server->proxyType())->toBe('TRAEFIK');
});
it('uses a qemu vm as the localhost sentinel without changing its identity', function () {
$this->seed(ServerSeeder::class);
@@ -240,7 +240,7 @@ test('livewire migrateTo rejects cross-team destination', function () {
Livewire::test(ResourceOperations::class, ['resource' => $application])
->set('migrateVolumeData', false)
->call('migrateTo', $otherDestination->uuid)
->assertHasErrors('destination_id');
->assertDispatched('error', 'Destination not found.');
$application->refresh();
expect($application->destination_id)->toBe($this->destination->id);
@@ -322,6 +322,73 @@ test('rejects migration to a server that is not validated and reachable', functi
MigrateResourceToDestination::run($application, $this->targetDestination, migrateVolumes: false);
})->throws(ValidationException::class);
test('rejects migrating an application with a domain when the target proxy is not running', function () {
StopApplication::shouldNotRun();
$this->targetServer->proxy->set('type', 'TRAEFIK');
$this->targetServer->proxy->set('status', 'exited');
$this->targetServer->save();
$application = createMigrateTestApplication($this);
$application->update(['fqdn' => 'http://app.example.com']);
MigrateResourceToDestination::run($application, $this->targetDestination, migrateVolumes: false);
})->throws(ValidationException::class, 'The proxy of the target server is not running.');
test('rejects migrating a service with a domain when the target proxy is not running', function () {
StopService::shouldNotRun();
$service = Service::create([
'name' => 'svc-migrate',
'uuid' => new_public_id(),
'environment_id' => $this->environment->id,
'server_id' => $this->server->id,
'destination_id' => $this->destination->id,
'destination_type' => $this->destination->getMorphClass(),
'docker_compose_raw' => base64_encode("services:\n app:\n image: nginx\n"),
'docker_compose' => base64_encode("services:\n app:\n image: nginx\n"),
]);
$service->applications()->create(['name' => 'app', 'image' => 'nginx', 'fqdn' => 'http://svc.example.com']);
MigrateResourceToDestination::run($service, $this->targetDestination, migrateVolumes: false);
})->throws(ValidationException::class, 'The proxy of the target server is not running.');
test('livewire migrateTo shows why the migration was rejected', function () {
$this->actingAs($this->user);
session(['currentTeam' => $this->team]);
StopApplication::shouldNotRun();
$this->targetServer->proxy->set('type', 'TRAEFIK');
$this->targetServer->proxy->set('status', 'exited');
$this->targetServer->save();
$application = createMigrateTestApplication($this);
$application->update(['fqdn' => 'http://app.example.com']);
Livewire::test(ResourceOperations::class, ['resource' => $application])
->call('migrateTo', $this->targetDestination->uuid)
->assertDispatched('error', 'The proxy of the target server is not running. Start it on the server page before you migrate a resource with domains.')
->assertNoRedirect();
});
test('migrates an application with a domain when the target proxy does not block it', function (string $proxyType, string $proxyStatus) {
StopApplication::shouldRun()->once();
$this->targetServer->proxy->set('type', $proxyType);
$this->targetServer->proxy->set('status', $proxyStatus);
$this->targetServer->save();
$application = createMigrateTestApplication($this);
$application->update(['fqdn' => 'http://app.example.com']);
MigrateResourceToDestination::run($application, $this->targetDestination, migrateVolumes: false);
expect($application->refresh()->destination_id)->toBe($this->targetDestination->id);
})->with([
'proxy running' => ['TRAEFIK', 'running'],
'no managed proxy' => ['NONE', 'exited'],
]);
test('migration is unavailable outside development mode', function () {
config(['app.env' => 'production']);
StopApplication::shouldNotRun();