fix(engine): one host per public IP (unique constraint + validation)
Prevents two onboarding runs from fighting over the same physical server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
48ed5e7d34
commit
91e308efb0
|
|
@ -16,7 +16,7 @@ class HostCreate extends Component
|
||||||
#[Validate('required|string|in:fsn,hel')]
|
#[Validate('required|string|in:fsn,hel')]
|
||||||
public string $datacenter = 'fsn';
|
public string $datacenter = 'fsn';
|
||||||
|
|
||||||
#[Validate('required|ip')]
|
#[Validate('required|ip|unique:hosts,public_ip')]
|
||||||
public string $public_ip = '';
|
public string $public_ip = '';
|
||||||
|
|
||||||
#[Validate('required|string|min:8')]
|
#[Validate('required|string|min:8')]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class HostFactory extends Factory
|
||||||
return [
|
return [
|
||||||
'name' => 'pve-test-'.$this->faker->unique()->numberBetween(1, 99999),
|
'name' => 'pve-test-'.$this->faker->unique()->numberBetween(1, 99999),
|
||||||
'datacenter' => 'fsn',
|
'datacenter' => 'fsn',
|
||||||
'public_ip' => $this->faker->ipv4(),
|
'public_ip' => $this->faker->unique()->ipv4(),
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// One physical server = one host record: prevents two onboarding runs
|
||||||
|
// from fighting over the same machine's SSH/WireGuard/token config.
|
||||||
|
Schema::table('hosts', function (Blueprint $table) {
|
||||||
|
$table->unique('public_ip');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('hosts', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['public_ip']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -58,6 +58,19 @@ it('creates a host and starts onboarding with an encrypted password', function (
|
||||||
Queue::assertPushed(AdvanceRunJob::class);
|
Queue::assertPushed(AdvanceRunJob::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects a duplicate public ip', function () {
|
||||||
|
Host::factory()->create(['public_ip' => '203.0.113.99']);
|
||||||
|
|
||||||
|
Livewire::actingAs(admin())
|
||||||
|
->test(HostCreate::class)
|
||||||
|
->set('name', 'pve-dup')
|
||||||
|
->set('datacenter', 'fsn')
|
||||||
|
->set('public_ip', '203.0.113.99')
|
||||||
|
->set('root_password', 'supersecret')
|
||||||
|
->call('save')
|
||||||
|
->assertHasErrors(['public_ip']);
|
||||||
|
});
|
||||||
|
|
||||||
it('validates the add-host form', function () {
|
it('validates the add-host form', function () {
|
||||||
Livewire::actingAs(admin())
|
Livewire::actingAs(admin())
|
||||||
->test(HostCreate::class)
|
->test(HostCreate::class)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue