From c46dcd95b1b4d1fe49c263763932ce399f9b5482 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Thu, 21 May 2026 09:45:21 -0400 Subject: [PATCH] fix: no-op delegated domains without state --- internal/drivers/delegation.go | 11 ++++++++--- internal/drivers/delegation_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/internal/drivers/delegation.go b/internal/drivers/delegation.go index abde479..1ac5acb 100644 --- a/internal/drivers/delegation.go +++ b/internal/drivers/delegation.go @@ -245,12 +245,17 @@ func (d *DelegationDriver) Delete(ctx context.Context, ref interfaces.ResourceRe // Diff compares desired vs current. Multiset semantics on nameservers // (order-independent — Hover accepts any order on PUT). Domain rename // (desired vs current.ProviderID) forces Replace. -func (d *DelegationDriver) Diff(_ context.Context, desired interfaces.ResourceSpec, current *interfaces.ResourceOutput) (*interfaces.DiffResult, error) { +func (d *DelegationDriver) Diff(ctx context.Context, desired interfaces.ResourceSpec, current *interfaces.ResourceOutput) (*interfaces.DiffResult, error) { s, err := parseDelegationSpec(desired) if err != nil { return nil, err } if current == nil { + if d.nsResolver != nil { + if ns, err := d.nsResolver(ctx, s.domain); err == nil && sameNameserverSet(ns, s.nameservers) { + return &interfaces.DiffResult{NeedsUpdate: false}, nil + } + } return &interfaces.DiffResult{NeedsUpdate: true}, nil } if current.ProviderID != "" && !strings.EqualFold(s.domain, current.ProviderID) { @@ -316,10 +321,10 @@ func sameNameserverSet(a, b []string) bool { sa := make([]string, len(a)) sb := make([]string, len(b)) for i, s := range a { - sa[i] = strings.ToLower(s) + sa[i] = strings.ToLower(normalizeNameserverHost(s)) } for i, s := range b { - sb[i] = strings.ToLower(s) + sb[i] = strings.ToLower(normalizeNameserverHost(s)) } sort.Strings(sa) sort.Strings(sb) diff --git a/internal/drivers/delegation_test.go b/internal/drivers/delegation_test.go index 1866516..d6319f2 100644 --- a/internal/drivers/delegation_test.go +++ b/internal/drivers/delegation_test.go @@ -258,6 +258,33 @@ func TestDelegationDriver_Diff_NilCurrent(t *testing.T) { } } +func TestDelegationDriver_Diff_NilCurrent_UsesPublicNSNoop(t *testing.T) { + fc := &fakeDelegationClient{getErr: errors.New("Hover login should not be needed")} + d := NewDelegationDriverWithClientAndResolver(fc, func(_ context.Context, domain string) ([]string, error) { + if domain != "example.com" { + t.Fatalf("resolver domain = %q, want example.com", domain) + } + return []string{"ns2.digitalocean.com.", "ns1.digitalocean.com.", "ns3.digitalocean.com."}, nil + }) + spec := interfaces.ResourceSpec{ + Name: "example.com", Type: "infra.dns_delegation", + Config: map[string]any{ + "domain": "example.com", + "nameservers": []any{"ns1.digitalocean.com", "ns2.digitalocean.com", "ns3.digitalocean.com"}, + }, + } + res, err := d.Diff(context.Background(), spec, nil) + if err != nil { + t.Fatalf("Diff: %v", err) + } + if res.NeedsUpdate { + t.Fatalf("expected NeedsUpdate=false when public NS already matches desired, got %+v", res) + } + if fc.getCalls != 0 { + t.Fatalf("GetDomainDelegation called %d times; public NS should avoid Hover login", fc.getCalls) + } +} + func TestDelegationDriver_Diff_UpToDate_OrderIndependent(t *testing.T) { d := NewDelegationDriverWithClient(&fakeDelegationClient{}) spec := interfaces.ResourceSpec{