fix(datacenters): a code becomes a DNS label, so validate it as one
alpha_dash accepted eu_west, -edge and edge-, none of which are valid DNS labels — every host in such a datacenter would have failed registration and, because DNS is non-fatal, silently ended up without a name. The console now requires a proper label, and the step normalises anything created before that rule so existing rows still get a usable name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
69043f9ffc
commit
e96f4c1c1a
|
|
@ -11,7 +11,7 @@ use Livewire\Component;
|
|||
#[Layout('layouts.admin')]
|
||||
class Datacenters extends Component
|
||||
{
|
||||
#[Validate('required|string|max:8|alpha_dash|unique:datacenters,code')]
|
||||
#[Validate(['required', 'string', 'max:8', 'regex:/^[a-z0-9]([a-z0-9-]{0,6}[a-z0-9])?$/', 'unique:datacenters,code'])]
|
||||
public string $code = '';
|
||||
|
||||
#[Validate('required|string|max:255')]
|
||||
|
|
@ -31,7 +31,10 @@ class Datacenters extends Component
|
|||
// Country must be one of the curated list — a forged request can bypass
|
||||
// the <select>, so enforce membership server-side (mirrors EditDatacenter).
|
||||
$data = $this->validate([
|
||||
'code' => 'required|string|max:8|alpha_dash|unique:datacenters,code',
|
||||
// A datacenter code becomes a DNS label (fsn-01.node.clupilot.com),
|
||||
// so it has to be one: lowercase, no underscores, no leading or
|
||||
// trailing dash.
|
||||
'code' => ['required', 'string', 'max:8', 'regex:/^[a-z0-9]([a-z0-9-]{0,6}[a-z0-9])?$/', 'unique:datacenters,code'],
|
||||
'name' => 'required|string|max:255',
|
||||
'location' => ['nullable', Rule::in(array_keys((array) config('countries')))],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -84,7 +84,11 @@ class RegisterHostDns extends HostStep
|
|||
->max() ?? 0;
|
||||
$next = max($next, $inUse + 1);
|
||||
|
||||
$name = sprintf('%s-%02d', $host->datacenter, $next);
|
||||
// Datacenter codes created before the rule was tightened can still
|
||||
// hold characters a DNS label cannot; a name that the provider
|
||||
// rejects would leave the host nameless forever.
|
||||
$label = trim(preg_replace('/[^a-z0-9-]+/', '-', strtolower($host->datacenter)) ?? '', '-');
|
||||
$name = sprintf('%s-%02d', $label !== '' ? $label : 'node', $next);
|
||||
|
||||
Settings::set($key, $next);
|
||||
$host->update(['dns_name' => $name]); // reserved while still locked
|
||||
|
|
|
|||
|
|
@ -344,3 +344,20 @@ it('keeps the host until its DNS record is really gone', function () {
|
|||
expect(App\Models\Host::query()->whereKey($host->id)->exists())->toBeFalse()
|
||||
->and($s['dns']->records)->toBe([]);
|
||||
});
|
||||
|
||||
it('builds a valid DNS label even from an awkward datacenter code', function () {
|
||||
$s = fakeServices();
|
||||
// Inserted directly: the console now rejects this shape, but rows created
|
||||
// before the rule was tightened still exist.
|
||||
App\Models\Datacenter::factory()->create(['code' => 'eu_west', 'name' => 'EU West']);
|
||||
$host = App\Models\Host::factory()->active()->create([
|
||||
'datacenter' => 'eu_west', 'wg_ip' => '10.66.0.70', 'dns_name' => null,
|
||||
]);
|
||||
$run = App\Models\ProvisioningRun::factory()->forHost($host)->create(['pipeline' => 'host']);
|
||||
|
||||
(new App\Provisioning\Steps\Host\RegisterHostDns($s['dns']))->execute($run);
|
||||
|
||||
$name = $host->fresh()->dns_name;
|
||||
expect($name)->toBe('eu-west-01')
|
||||
->and($name)->toMatch('/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue