Files
coolify/app/Jobs/ReleaseRemovedDnsHostnamesJob.php
Andras BacsaiandClaude Opus 5.5 2063e84d7c fix(dns): release managed records when a domain edit removes a hostname
- Editing a domain (UI application, service and preview domains, the
  General form, API application, preview, service, and service
  application updates) now releases the hostnames that the edit
  removed, like removing a domain does. Records that another live
  resource still uses are kept.
- A per-hostname lock (managed-dns:{team}:{hostname}) now covers the
  release (re-check, Cloudflare delete, row delete) and the create
  paths. A release can no longer delete a record that is being added
  again; a busy lock makes the release retry and the create wait or
  retry, and a create after a release makes the record again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 18:06:37 +02:00

78 lines
2.3 KiB
PHP

<?php
namespace App\Jobs;
use App\Services\Dns\ManagedDnsRecordCleanup;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
/**
* Releases the managed DNS records of hostnames that a live resource no longer uses after a domain edit
* (API and other non-interactive paths). Records still used by another live resource stay; only owned records
* are deleted. Provider errors and a busy hostname lock are retried a few times.
*/
class ReleaseRemovedDnsHostnamesJob implements ShouldQueue
{
use Queueable;
public const MAX_ATTEMPTS = 3;
public int $tries = self::MAX_ATTEMPTS;
public int $timeout = 120;
/**
* @param array<int, string> $hostnames
*/
public function __construct(
public string $resourceType,
public int|string $resourceId,
public int $teamId,
public array $hostnames,
) {}
public function handle(ManagedDnsRecordCleanup $cleanup): void
{
$resource = $this->resource();
if ($resource === null) {
// A deleted resource releases all of its records through ReleaseManagedDnsRecordsJob.
return;
}
$retry = false;
foreach ($this->hostnames as $hostname) {
if ($cleanup->releaseHostname($resource, $hostname, $this->teamId)?->shouldRetry()) {
$retry = true;
}
}
if (! $retry) {
return;
}
if ($this->attempts() < self::MAX_ATTEMPTS) {
$this->release(60 * $this->attempts());
return;
}
Log::warning('Managed DNS records of removed hostnames could not be cleaned up; they were left in place.', [
'resource_type' => $this->resourceType,
'resource_id' => $this->resourceId,
'hostnames' => $this->hostnames,
]);
}
private function resource(): ?Model
{
$resourceClass = Relation::getMorphedModel($this->resourceType) ?? $this->resourceType;
if (! is_subclass_of($resourceClass, Model::class)) {
return null;
}
return $resourceClass::query()->find($this->resourceId);
}
}