From c073f2e6543cdfdd388e681ac0d927f85f1d322f Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Mon, 1 Dec 2025 15:08:43 +0100 Subject: [PATCH] HETZNER: gracefully handle FQDN labels when listing records (#3859) - Closes https://github.com/StackExchange/dnscontrol/issues/3853 This PR is gracefully handling FQDN labels when listing records from the Hetzner DNS Control api. These records can be created via other tools or the browser UI. Testing: ```diff diff --git a/providers/hetzner/types.go b/providers/hetzner/types.go index 964f1b7b..3429acc2 100644 --- a/providers/hetzner/types.go +++ b/providers/hetzner/types.go @@ -3,2 +3,3 @@ package hetzner import ( + "fmt" "strings" @@ -63,3 +64,3 @@ func fromRecordConfig(in *models.RecordConfig, zone zone) record { r := record{ - Name: in.GetLabel(), + Name: in.GetLabelFQDN() + ".", Type: in.Type, @@ -69,2 +70,3 @@ func fromRecordConfig(in *models.RecordConfig, zone zone) record { } + fmt.Printf("CREATE: %q\n", r.Name) @@ -93,2 +95,3 @@ func toRecordConfig(domain string, r *record) (*models.RecordConfig, error) { } + fmt.Printf("LISTING: %q\n", r.Name) if strings.HasSuffix(r.Name, "."+domain+".") { ``` Config: ```js var REG_NONE = NewRegistrar('none') var DSP = NewDnsProvider("HETZNER") D('testing1.dev', REG_NONE, DnsProvider(DSP), A('@', '127.0.0.1'), A('foo', '127.0.0.1') ) ``` First push: ``` Waiting for concurrent gathering(s) to complete...LISTING: "@" LISTING: "@" LISTING: "@" LISTING: "@" CREATE: "foo.testing1.dev." DONE ******************** Domain: testing1.dev 1 correction (HETZNER) #1: Batch creation of records: + CREATE A foo.testing1.dev 127.0.0.1 ttl=300 SUCCESS! Done. 1 corrections. ``` Second push (no-op): ``` Waiting for concurrent gathering(s) to complete...LISTING: "@" LISTING: "@" LISTING: "@" LISTING: "@" LISTING: "foo.testing1.dev." DONE ******************** Domain: testing1.dev Done. 0 corrections. ``` DNS query: ``` $ dig foo.testing1.dev. @helium.ns.hetzner.de. ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53563 foo.testing1.dev. 300 IN A 127.0.0.1 ``` Additional testing: - update/delete `foo` when record `foo.testing1.dev.` exists, works - creating `foo.testing1.dev` is treated as `foo.testing1.dev.testing1.dev.` in the API, hence the specific suffix check for `DOMAIN` - Test with HETZNER_V2, rejects records with FQDN ``` FAILURE! has dot (.) suffix (invalid_input, 50f9cf872ed8f1f808fd33c25cf88a81) ``` --- providers/hetzner/types.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/providers/hetzner/types.go b/providers/hetzner/types.go index 40b17ad43..964f1b7bb 100644 --- a/providers/hetzner/types.go +++ b/providers/hetzner/types.go @@ -1,6 +1,8 @@ package hetzner import ( + "strings" + "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/txtutil" ) @@ -89,7 +91,12 @@ func toRecordConfig(domain string, r *record) (*models.RecordConfig, error) { TTL: *r.TTL, Original: r, } - rc.SetLabel(r.Name, domain) + if strings.HasSuffix(r.Name, "."+domain+".") { + // Records created through other tools or the browser UI can contain FQDN labels. + rc.SetLabelFromFQDN(r.Name, domain) + } else { + rc.SetLabel(r.Name, domain) + } // HACK: Hetzner is inserting a trailing space after multiple, quoted values. // NOTE: The actual DNS answer does not contain the space.