From 7e3d652673d9595def10a71090b7c536f59d3e54 Mon Sep 17 00:00:00 2001 From: Tatsuro Shibamura Date: Sat, 8 Aug 2026 12:02:33 +0900 Subject: [PATCH] Fix IONOS DNS record creation --- deploy/uiFormDefinition.json | 2 +- docs/guide/dns-providers.md | 6 ++-- docs/reference/configuration.md | 2 +- src/Acmebot.App/Providers/IonosDnsProvider.cs | 33 +++++++++++-------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/deploy/uiFormDefinition.json b/deploy/uiFormDefinition.json index 9f350e4a..b4d8efa1 100644 --- a/deploy/uiFormDefinition.json +++ b/deploy/uiFormDefinition.json @@ -989,7 +989,7 @@ "password": "API key", "confirmPassword": "Confirm API key" }, - "toolTip": "IONOS DNS API key.", + "toolTip": "IONOS DNS API key in public-prefix.secret format.", "constraints": { "required": true }, diff --git a/docs/guide/dns-providers.md b/docs/guide/dns-providers.md index a8799989..8a08319a 100644 --- a/docs/guide/dns-providers.md +++ b/docs/guide/dns-providers.md @@ -268,14 +268,14 @@ https://www.googleapis.com/auth/ndev.clouddns.readwrite ## IONOS DNS -Use an IONOS DNS API key that can list zones and manage DNS records. +Create an API key as described in the [IONOS API getting started guide](https://developer.hosting.ionos.com/docs/getstarted). The value consists of the public prefix and secret joined by a period. Acmebot uses the [IONOS DNS API](https://developer.hosting.ionos.com/docs/dns) to list zones and manage DNS records. | Option | Description | | --- | --- | -| `ApiKey` | IONOS DNS API key sent in the `X-API-Key` header. | +| `ApiKey` | IONOS DNS API key in `.` format, sent in the `X-API-Key` header. | ```text -Acmebot__IonosDns__ApiKey= +Acmebot__IonosDns__ApiKey=. ``` ## OVH diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index fbe5535d..af6c682c 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -138,7 +138,7 @@ Acmebot uses the Google Cloud DNS read/write OAuth scope and ignores private man | Setting | Description | | --- | --- | -| `Acmebot__IonosDns__ApiKey` | IONOS DNS API key sent in the `X-API-Key` header. | +| `Acmebot__IonosDns__ApiKey` | IONOS DNS API key in `.` format, sent in the `X-API-Key` header. See the [IONOS API getting started guide](https://developer.hosting.ionos.com/docs/getstarted). | ### OVH diff --git a/src/Acmebot.App/Providers/IonosDnsProvider.cs b/src/Acmebot.App/Providers/IonosDnsProvider.cs index 861490d8..9ae46bc6 100644 --- a/src/Acmebot.App/Providers/IonosDnsProvider.cs +++ b/src/Acmebot.App/Providers/IonosDnsProvider.cs @@ -32,17 +32,17 @@ public async Task CreateTxtRecordAsync(DnsZone zone, string relativeRecordName, { var recordName = DnsRecordName.ToFqdn(zone.Name, relativeRecordName); - foreach (var value in values) + var records = values.Select(value => new RecordParam { - var record = new RecordParam - { - Name = recordName, - Type = "TXT", - Content = value, - Ttl = 60 - }; + Name = recordName, + Type = "TXT", + Content = value, + Ttl = 60 + }).ToArray(); - await _ionosDnsClient.CreateRecordAsync(zone.Id, record, cancellationToken); + if (records.Length != 0) + { + await _ionosDnsClient.CreateRecordsAsync(zone.Id, records, cancellationToken); } } @@ -97,21 +97,28 @@ public async IAsyncEnumerable ListZonesAsync([EnumeratorCancellation] Canc public async Task> ListRecordsAsync(string zoneId, string recordName, CancellationToken cancellationToken = default) { - var result = await _httpClient.GetFromJsonAsync($"zones/{zoneId}?recordName={recordName}&recordType=TXT", cancellationToken); + var result = await _httpClient.GetFromJsonAsync( + $"zones/{zoneId}?recordName={recordName}&recordType=TXT", + cancellationToken); return result?.Records ?? []; } - public async Task CreateRecordAsync(string zoneId, RecordParam record, CancellationToken cancellationToken = default) + public async Task CreateRecordsAsync(string zoneId, RecordParam[] records, CancellationToken cancellationToken = default) { - var response = await _httpClient.PostAsJsonAsync($"zones/{zoneId}/records", record, cancellationToken); + using var response = await _httpClient.PostAsJsonAsync( + $"zones/{zoneId}/records", + records, + cancellationToken); response.EnsureSuccessStatusCode(); } public async Task DeleteRecordAsync(string zoneId, string recordId, CancellationToken cancellationToken = default) { - var response = await _httpClient.DeleteAsync($"zones/{zoneId}/records/{recordId}", cancellationToken); + using var response = await _httpClient.DeleteAsync( + $"zones/{zoneId}/records/{recordId}", + cancellationToken); response.EnsureSuccessStatusCode(); }