65 lines
2.1 KiB
PHP
65 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Dns;
|
|
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Support\Facades\Http;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Hetzner DNS API client. Zone + token from config/provisioning.php.
|
|
* Not unit-tested (live I/O).
|
|
*/
|
|
class HttpHetznerDnsClient implements HetznerDnsClient
|
|
{
|
|
private function http(): PendingRequest
|
|
{
|
|
return Http::withHeaders(['Auth-API-Token' => (string) config('provisioning.dns.token')])
|
|
->baseUrl('https://dns.hetzner.com/api/v1')
|
|
->timeout(15);
|
|
}
|
|
|
|
private function zoneId(): string
|
|
{
|
|
$zone = (string) config('provisioning.dns.zone');
|
|
$zones = $this->http()->get('/zones', ['name' => $zone])->throw()->json('zones', []);
|
|
|
|
return $zones[0]['id'] ?? throw new RuntimeException("DNS zone {$zone} not found");
|
|
}
|
|
|
|
public function upsertRecord(string $fqdn, string $type, string $value): string
|
|
{
|
|
$zoneId = $this->zoneId();
|
|
$name = rtrim(str_replace('.'.config('provisioning.dns.zone'), '', $fqdn), '.');
|
|
|
|
$existing = collect($this->http()->get('/records', ['zone_id' => $zoneId])->throw()->json('records', []))
|
|
->first(fn ($r) => $r['name'] === $name && $r['type'] === $type);
|
|
|
|
if ($existing) {
|
|
$this->http()->put("/records/{$existing['id']}", [
|
|
'zone_id' => $zoneId, 'type' => $type, 'name' => $name, 'value' => $value, 'ttl' => 300,
|
|
])->throw();
|
|
|
|
return (string) $existing['id'];
|
|
}
|
|
|
|
return (string) $this->http()->post('/records', [
|
|
'zone_id' => $zoneId, 'type' => $type, 'name' => $name, 'value' => $value, 'ttl' => 300,
|
|
])->throw()->json('record.id');
|
|
}
|
|
|
|
public function deleteRecord(string $recordId): void
|
|
{
|
|
$response = $this->http()->delete("/records/{$recordId}");
|
|
|
|
// A record that is already gone is the outcome we wanted. Without this,
|
|
// a lost response or a retry after a later failure turns every further
|
|
// attempt into a 404 — and a host that can then never be purged.
|
|
if ($response->notFound()) {
|
|
return;
|
|
}
|
|
|
|
$response->throw();
|
|
}
|
|
}
|