Merge remote-tracking branch 'origin/main'

This commit is contained in:
Andras Bacsai
2026-09-25 20:38:07 +02:00
5 changed files with 178 additions and 13 deletions
+11 -4
View File
@@ -775,8 +775,11 @@ class Domains extends Component
/**
* Persist current domain row DNS results and drop statuses for removed domains.
*
* @param array<int, string> $startingCheckKeys Status keys whose check is being started by this call.
* They are allowed to replace a stored completed result.
*/
protected function persistDomainDnsStatuses(): void
protected function persistDomainDnsStatuses(array $startingCheckKeys = []): void
{
$statuses = [];
@@ -808,11 +811,15 @@ class Domains extends Component
];
}
DB::transaction(function () use (&$statuses): void {
DB::transaction(function () use (&$statuses, $startingCheckKeys): void {
$application = Application::query()->lockForUpdate()->findOrFail($this->application->id);
$storedStatuses = $application->domain_dns_statuses ?? [];
foreach ($statuses as $key => $status) {
if (in_array($key, $startingCheckKeys, true)) {
continue;
}
$localCheckId = $status['check_id'] ?? null;
$storedCheckId = $storedStatuses[$key]['check_id'] ?? null;
@@ -1054,7 +1061,7 @@ class Domains extends Component
foreach ($dnsChecks as $dnsCheck) {
$this->markUrlsAsChecking([$dnsCheck['url']], $serviceForCheck, $dnsCheck['check_id']);
}
$this->persistDomainDnsStatuses();
$this->persistDomainDnsStatuses($dnsChecks->pluck('status_key')->all());
$failedDnsChecks = 0;
foreach ($dnsChecks as $dnsCheck) {
@@ -1207,7 +1214,7 @@ class Domains extends Component
foreach ($this->dnsEntriesForUrls($urls, $service) as $statusKey => $url) {
$checkId = new_public_id();
$this->markUrlsAsChecking([$url], $service, $checkId);
$this->persistDomainDnsStatuses();
$this->persistDomainDnsStatuses([$statusKey]);
try {
CheckDomainDnsJob::dispatch(
@@ -339,17 +339,18 @@ class PreviewDomains extends Component
}
$row = $this->domainRows[$index];
$statusKey = $this->statusKey($row['url'], $row['service']);
$checkId = new_public_id();
$this->domainRows[$index]['dns_status'] = 'checking';
$this->domainRows[$index]['dns_message'] = 'Checking DNS...';
$this->domainRows[$index]['check_id'] = $checkId;
$this->persistDnsStatuses();
$this->persistDnsStatuses([$statusKey]);
try {
$server = $this->preview->application->destination?->server;
CheckDomainDnsJob::dispatch(
$this->preview,
$this->statusKey($row['url'], $row['service']),
$statusKey,
$row['url'],
$server,
$server ? serverDnsTargetIp($server) ?? $server->ip : null,
@@ -494,7 +495,11 @@ class PreviewDomains extends Component
return true;
}
private function persistDnsStatuses(): void
/**
* @param array<int, string> $startingCheckKeys Status keys whose check is being started by this call.
* They are allowed to replace a stored completed result.
*/
private function persistDnsStatuses(array $startingCheckKeys = []): void
{
$statuses = [];
foreach ($this->domainRows as $row) {
@@ -505,13 +510,13 @@ class PreviewDomains extends Component
];
}
DB::transaction(function () use (&$statuses): void {
DB::transaction(function () use (&$statuses, $startingCheckKeys): void {
$preview = ApplicationPreview::query()->lockForUpdate()->findOrFail($this->preview->id);
$storedStatuses = $preview->domain_dns_statuses ?? [];
foreach ($statuses as $key => $status) {
$storedStatus = $storedStatuses[$key] ?? null;
if (! is_array($storedStatus)) {
if (! is_array($storedStatus) || in_array($key, $startingCheckKeys, true)) {
continue;
}
+12 -4
View File
@@ -577,7 +577,11 @@ class Domains extends Component
$this->persistAllDomainDnsStatuses();
}
protected function persistAllDomainDnsStatuses(): void
/**
* @param array<int, string> $startingCheckKeys Status keys whose check is being started by this call.
* They are allowed to replace a stored completed result.
*/
protected function persistAllDomainDnsStatuses(array $startingCheckKeys = []): void
{
$byApp = [];
@@ -617,11 +621,15 @@ class Domains extends Component
->all();
$statuses = array_intersect_key($statuses, array_flip($currentUrls));
DB::transaction(function () use ($app, &$statuses): void {
DB::transaction(function () use ($app, &$statuses, $startingCheckKeys): void {
$application = ServiceApplication::query()->lockForUpdate()->findOrFail($app->id);
$storedStatuses = $application->domain_dns_statuses ?? [];
foreach ($statuses as $key => $status) {
if (in_array($key, $startingCheckKeys, true)) {
continue;
}
$localCheckId = $status['check_id'] ?? null;
$storedCheckId = $storedStatuses[$key]['check_id'] ?? null;
@@ -1096,7 +1104,7 @@ class Domains extends Component
foreach ($dnsChecks as $dnsCheck) {
$this->markUrlsAsChecking([$dnsCheck['url']], $serviceApplicationId, $dnsCheck['check_id']);
}
$this->persistAllDomainDnsStatuses();
$this->persistAllDomainDnsStatuses($dnsChecks->pluck('url')->all());
$failedDnsChecks = 0;
foreach ($dnsChecks as $dnsCheck) {
@@ -1655,7 +1663,7 @@ class Domains extends Component
foreach (array_unique($urls) as $url) {
$checkId = new_public_id();
$this->markUrlsAsChecking([$url], (int) $application->id, $checkId);
$this->persistAllDomainDnsStatuses();
$this->persistAllDomainDnsStatuses([$url]);
try {
CheckDomainDnsJob::dispatch(
+117
View File
@@ -3558,3 +3558,120 @@ it('prevents members from cancelling protected application redirect conflict sta
->set('showDomainConflictModal', false)
->assertForbidden();
});
it('restarts a dns check when the domain already has a completed result', function () {
Queue::fake();
$url = 'https://dns-recheck.example.com';
$this->application->update([
'fqdn' => $url,
'domain_dns_statuses' => [
$url => [
'status' => 'failed',
'message' => 'Required DNS record type A pointing to 203.0.113.10',
'expected_ip' => '203.0.113.10',
'checked_at' => now()->subDay()->toIso8601String(),
],
],
]);
Livewire::test(Domains::class, ['application' => $this->application->fresh()])
->call('checkDomainDns', 0)
->assertSet('domainRows.0.dns_status', 'checking');
expect($this->application->fresh()->domain_dns_statuses[$url]['status'])->toBe('checking');
Queue::assertPushed(CheckDomainDnsJob::class);
});
it('does not overwrite a completed dns result with stale checking state', function () {
$url = 'https://dns-stale.example.com';
$this->application->update([
'fqdn' => $url,
'domain_dns_statuses' => [
$url => [
'status' => 'checking',
'message' => 'Checking DNS...',
'check_id' => 'stale-check',
],
],
]);
$component = Livewire::test(Domains::class, ['application' => $this->application->fresh()]);
$this->application->update([
'domain_dns_statuses' => [
$url => [
'status' => 'ok',
'message' => 'DNS looks correct.',
'check_id' => 'completed-check',
],
],
]);
$method = new ReflectionMethod($component->instance(), 'persistDomainDnsStatuses');
$method->invoke($component->instance());
expect($this->application->fresh()->domain_dns_statuses[$url])
->toMatchArray([
'status' => 'ok',
'message' => 'DNS looks correct.',
'check_id' => 'completed-check',
]);
});
it('replaces a completed dns result when the domain is checked again', function () {
$settings = InstanceSettings::get();
$settings->is_dns_validation_enabled = true;
$settings->save();
$url = 'https://this-domain-should-not-resolve-for-coolify-tests.invalid';
$checkedAt = now()->subDays(12)->toIso8601String();
$this->application->update([
'fqdn' => $url,
'domain_dns_statuses' => [
$url => [
'status' => 'ok',
'message' => 'DNS looks correct.',
'expected_ip' => '203.0.113.10',
'checked_at' => $checkedAt,
],
],
]);
Livewire::test(Domains::class, ['application' => $this->application->fresh()])
->call('checkDomainDns', 0)
->call('pollDnsChecks')
->assertSet('domainRows.0.dns_status', 'failed');
expect($this->application->fresh()->domain_dns_statuses[$url])
->status->toBe('failed')
->checked_at->not->toBe($checkedAt);
});
it('restarts a preview dns check when the domain already has a completed result', function () {
Queue::fake();
$url = 'https://preview-recheck.example.com';
$statusKey = hash('sha256', $url.'|');
$preview = ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => 54,
'pull_request_html_url' => 'https://github.com/coollabsio/coolify/pull/54',
'fqdn' => $url,
'domain_dns_statuses' => [
$statusKey => [
'status' => 'failed',
'message' => 'Required DNS record type A pointing to 203.0.113.10',
],
],
]);
Livewire::test(PreviewDomains::class, ['preview' => $preview])
->call('checkDomainDns', 0)
->assertSet('domainRows.0.dns_status', 'checking');
expect($preview->fresh()->domain_dns_statuses[$statusKey]['status'])->toBe('checking');
Queue::assertPushed(CheckDomainDnsJob::class);
});
+28
View File
@@ -1319,3 +1319,31 @@ it('lays out the domain settings dropdowns in responsive columns', function () {
expect($view)->toContain('mt-4 grid grid-cols-1 gap-4 border-t border-neutral-200 pt-4 sm:grid-cols-2')
->toContain('flex flex-wrap items-center justify-between gap-2');
});
it('restarts a service dns check when the domain already has a completed result', function () {
Queue::fake();
$domain = 'https://api.example.com';
$this->apiApp->update([
'domain_dns_statuses' => [
$domain => [
'status' => 'failed',
'message' => 'Required DNS record type A pointing to 203.0.113.10',
'expected_ip' => '203.0.113.10',
'checked_at' => now()->subDay()->toIso8601String(),
],
],
]);
$component = Livewire::test(Domains::class, ['service' => $this->service->fresh(['applications', 'server'])]);
$index = collect($component->get('domainRows'))->search(fn (array $row): bool => $row['url'] === $domain);
expect($index)->not->toBeFalse();
$component->call('checkDomainDns', $index)
->assertSet("domainRows.{$index}.dns_status", 'checking');
expect($this->apiApp->fresh()->domain_dns_statuses[$domain]['status'])->toBe('checking');
Queue::assertPushed(CheckDomainDnsJob::class);
});