diff --git a/provider/alibabacloud/alibaba_cloud.go b/provider/alibabacloud/alibaba_cloud.go index f70979a33c..f835a1c684 100644 --- a/provider/alibabacloud/alibaba_cloud.go +++ b/provider/alibabacloud/alibaba_cloud.go @@ -633,45 +633,45 @@ func (p *AlibabaCloudProvider) equals(record alidns.Record, endpoint *endpoint.E return ttl1 == ttl2 } +// updateRecords updates, deletes, and creates DNS records to match the desired endpoints. func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]alidns.Record, endpoints []*endpoint.Endpoint, hostedZoneDomains []string) { for _, endpoint := range endpoints { key := p.getRecordKeyByEndpoint(endpoint) records := recordMap[key] - for _, record := range records { - value := record.Value - if record.Type == "TXT" { - value = p.unescapeTXTRecordValue(value) - } - found := false - for _, target := range endpoint.Targets { - // Find matched record to delete - if value == target { - found = true - } - } - if found { - if !p.equals(record, endpoint) { - // Update record - p.updateRecord(record, endpoint) - } - } else { - p.deleteRecord(record.RecordId) - } + p.updateOrDeleteRecords(records, endpoint) + p.createMissingRecords(records, endpoint, hostedZoneDomains) + } +} + +// updateOrDeleteRecords updates records that are different and deletes records that are not in the desired endpoints. +func (p *AlibabaCloudProvider) updateOrDeleteRecords(records []alidns.Record, endpoint *endpoint.Endpoint) { + for _, record := range records { + value := record.Value + if record.Type == "TXT" { + value = p.unescapeTXTRecordValue(value) } - for _, target := range endpoint.Targets { - if endpoint.RecordType == "TXT" { - target = p.escapeTXTRecordValue(target) - } - found := false - for _, record := range records { - // Find matched record to delete - if record.Value == target { - found = true - } - } - if !found { - p.createRecord(endpoint, target, hostedZoneDomains) + found := slices.Contains(endpoint.Targets, value) + if found { + if !p.equals(record, endpoint) { + p.updateRecord(record, endpoint) } + } else { + p.deleteRecord(record.RecordId) + } + } +} + +// createMissingRecords creates records that are missing. +func (p *AlibabaCloudProvider) createMissingRecords(records []alidns.Record, endpoint *endpoint.Endpoint, hostedZoneDomains []string) { + for _, target := range endpoint.Targets { + if endpoint.RecordType == "TXT" { + target = p.escapeTXTRecordValue(target) + } + found := slices.ContainsFunc(records, func(record alidns.Record) bool { + return record.Value == target + }) + if !found { + p.createRecord(endpoint, target, hostedZoneDomains) } } } @@ -1022,6 +1022,7 @@ func (p *AlibabaCloudProvider) equalsPrivateZone(record pvtz.Record, endpoint *e return ttl1 == ttl2 } +// updatePrivateZoneRecords updates, deletes, and creates records in the private zone to match the desired endpoints. func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) { zoneNames := keys(zones) for _, endpoint := range endpoints { @@ -1031,43 +1032,53 @@ func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[string]*alibab log.Errorf("Failed to update %s record named '%s' for Alibaba Cloud Private Zone: failed to find private zone '%s'", endpoint.RecordType, endpoint.DNSName, domain) continue } + p.updateOrDeletePrivateZoneRecords(zone, endpoint, rr) + p.createMissingPrivateZoneRecords(zones, zone, endpoint, rr) + } +} - for _, record := range zone.records { - if record.Rr != rr || record.Type != endpoint.RecordType { - continue - } - value := record.Value - if record.Type == "TXT" { - value = p.unescapeTXTRecordValue(value) - } - found := slices.Contains(endpoint.Targets, value) - if found { - if !p.equalsPrivateZone(record, endpoint) { - // Update record - p.updatePrivateZoneRecord(record, endpoint) - } - } else { - p.deletePrivateZoneRecord(record.RecordId) - } +// updateOrDeletePrivateZoneRecords updates records in the private zone that are different and +// deletes records in the private zone that are not in the desired endpoint. +func (p *AlibabaCloudProvider) updateOrDeletePrivateZoneRecords( + zone *alibabaPrivateZone, + endpoint *endpoint.Endpoint, + rr string, +) { + for _, record := range zone.records { + if record.Rr != rr || record.Type != endpoint.RecordType { + continue } - for _, target := range endpoint.Targets { - if endpoint.RecordType == "TXT" { - target = p.escapeTXTRecordValue(target) - } - found := false - for _, record := range zone.records { - if record.Rr != rr || record.Type != endpoint.RecordType { - continue - } - // Find matched record to delete - if record.Value == target { - found = true - break - } - } - if !found { - p.createPrivateZoneRecord(zones, endpoint, target) + value := record.Value + if record.Type == "TXT" { + value = p.unescapeTXTRecordValue(value) + } + found := slices.Contains(endpoint.Targets, value) + if found { + if !p.equalsPrivateZone(record, endpoint) { + p.updatePrivateZoneRecord(record, endpoint) } + } else { + p.deletePrivateZoneRecord(record.RecordId) + } + } +} + +// createMissingPrivateZoneRecords creates records in the private zone that are missing. +func (p *AlibabaCloudProvider) createMissingPrivateZoneRecords( + zones map[string]*alibabaPrivateZone, + zone *alibabaPrivateZone, + endpoint *endpoint.Endpoint, + rr string, +) { + for _, target := range endpoint.Targets { + if endpoint.RecordType == "TXT" { + target = p.escapeTXTRecordValue(target) + } + found := slices.ContainsFunc(zone.records, func(record pvtz.Record) bool { + return record.Rr == rr && record.Type == endpoint.RecordType && record.Value == target + }) + if !found { + p.createPrivateZoneRecord(zones, endpoint, target) } } } diff --git a/provider/alibabacloud/alibaba_cloud_test.go b/provider/alibabacloud/alibaba_cloud_test.go index 5274a0d3e6..293b47a78a 100644 --- a/provider/alibabacloud/alibaba_cloud_test.go +++ b/provider/alibabacloud/alibaba_cloud_test.go @@ -439,69 +439,106 @@ func TestAlibabaCloudProvider_ApplyChanges_PrivateZone(t *testing.T) { func TestAlibabaCloudProvider_splitDNSName(t *testing.T) { p := newTestAlibabaCloudProvider(false) - endpoint := &endpoint.Endpoint{} - hostedZoneDomains := []string{"container-service.top", "example.org"} - + type testCase struct { + name string + dnsName string + zones []string + wantRR string + wantDomain string + } var emptyZoneDomains []string - - endpoint.DNSName = "www.example.org" - rr, domain := p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "www" || domain != "example.org" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = ".example.org" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "@" || domain != "example.org" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "www" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "@" || domain != "" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "@" || domain != "" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "_30000._tcp.container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "_30000._tcp" || domain != "container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "@" || domain != "container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "a.b.container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "a.b" || domain != "container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "a.b.c.container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, hostedZoneDomains) - if rr != "a.b.c" || domain != "container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - endpoint.DNSName = "a.b.c.container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, []string{"c.container-service.top"}) - if rr != "a.b" || domain != "c.container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - - endpoint.DNSName = "a.b.c.container-service.top" - rr, domain = p.splitDNSName(endpoint.DNSName, []string{"container-service.top", "c.container-service.top"}) - if rr != "a.b" || domain != "c.container-service.top" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - rr, domain = p.splitDNSName(endpoint.DNSName, emptyZoneDomains) - if rr != "@" || domain != "" { - t.Errorf("Failed to splitDNSName with emptyZoneDomains for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) - } - rr, domain = p.splitDNSName(endpoint.DNSName, []string{"example.com"}) - if rr != "@" || domain != "" { - t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) + cases := []testCase{ + { + name: "subdomain matches example.org zone", + dnsName: "www.example.org", + zones: []string{"container-service.top", "example.org"}, + wantRR: "www", + wantDomain: "example.org", + }, + { + name: "dot prefix matches example.org zone (root record)", + dnsName: ".example.org", + zones: []string{"container-service.top", "example.org"}, + wantRR: "@", + wantDomain: "example.org", + }, + { + name: "no domain match returns root RR", + dnsName: "www", + zones: []string{"container-service.top", "example.org"}, + wantRR: "@", + wantDomain: "", + }, + { + name: "empty DNS name returns root RR", + dnsName: "", + zones: []string{"container-service.top", "example.org"}, + wantRR: "@", + wantDomain: "", + }, + { + name: "SRV record with subdomain matches container-service.top", + dnsName: "_30000._tcp.container-service.top", + zones: []string{"container-service.top", "example.org"}, + wantRR: "_30000._tcp", + wantDomain: "container-service.top", + }, + { + name: "zone only matches container-service.top root", + dnsName: "container-service.top", + zones: []string{"container-service.top", "example.org"}, + wantRR: "@", + wantDomain: "container-service.top", + }, + { + name: "subdomain a.b matches container-service.top", + dnsName: "a.b.container-service.top", + zones: []string{"container-service.top", "example.org"}, + wantRR: "a.b", + wantDomain: "container-service.top", + }, + { + name: "subdomain a.b.c matches container-service.top", + dnsName: "a.b.c.container-service.top", + zones: []string{"container-service.top", "example.org"}, + wantRR: "a.b.c", + wantDomain: "container-service.top", + }, + { + name: "subdomain a.b matches c.container-service.top zone", + dnsName: "a.b.c.container-service.top", + zones: []string{"c.container-service.top"}, + wantRR: "a.b", + wantDomain: "c.container-service.top", + }, + { + name: "subdomain a.b matches most specific zone c.container-service.top", + dnsName: "a.b.c.container-service.top", + zones: []string{"container-service.top", "c.container-service.top"}, + wantRR: "a.b", + wantDomain: "c.container-service.top", + }, + { + name: "no zones configured returns root RR", + dnsName: "a.b.c.container-service.top", + zones: emptyZoneDomains, + wantRR: "@", + wantDomain: "", + }, + { + name: "no matching zone in list returns root RR", + dnsName: "a.b.c.container-service.top", + zones: []string{"example.com"}, + wantRR: "@", + wantDomain: "", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rr, domain := p.splitDNSName(tc.dnsName, tc.zones) + assert.Equal(t, tc.wantRR, rr, "%s: expected RR %q, got %q", tc.name, tc.wantRR, rr) + assert.Equal(t, tc.wantDomain, domain, "%s: expected domain %q, got %q", tc.name, tc.wantDomain, domain) + }) } }