external-dns/provider/cloudflare/cloudflare_regional_test.go
vflaux e2b56049f7
chore(cloudflare): use lib v4 for regional services (#5609)
* chore(cloudflare): add cloudflare v4 client

* chrome(cloudflare): cli v4 for regional hostanmes
2025-07-11 09:47:36 -07:00

1189 lines
28 KiB
Go

/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudflare
import (
"context"
"fmt"
"sort"
"strings"
"testing"
cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/cloudflare-go/v4/addressing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/plan"
)
func (m *mockCloudFlareClient) ListDataLocalizationRegionalHostnames(ctx context.Context, params addressing.RegionalHostnameListParams) autoPager[addressing.RegionalHostnameListResponse] {
zoneID := params.ZoneID.Value
if strings.Contains(zoneID, "rherror") {
return &mockAutoPager[addressing.RegionalHostnameListResponse]{err: fmt.Errorf("failed to list regional hostnames")}
}
results := make([]addressing.RegionalHostnameListResponse, 0, len(m.regionalHostnames[zoneID]))
for _, rh := range m.regionalHostnames[zoneID] {
results = append(results, addressing.RegionalHostnameListResponse{
Hostname: rh.hostname,
RegionKey: rh.regionKey,
})
}
return &mockAutoPager[addressing.RegionalHostnameListResponse]{
items: results,
}
}
func (m *mockCloudFlareClient) CreateDataLocalizationRegionalHostname(ctx context.Context, params addressing.RegionalHostnameNewParams) error {
if strings.Contains(params.Hostname.Value, "rherror") {
return fmt.Errorf("failed to create regional hostname")
}
m.Actions = append(m.Actions, MockAction{
Name: "CreateDataLocalizationRegionalHostname",
ZoneId: params.ZoneID.Value,
RecordId: "",
RegionalHostname: regionalHostname{
hostname: params.Hostname.Value,
regionKey: params.RegionKey.Value,
},
})
return nil
}
func (m *mockCloudFlareClient) UpdateDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameEditParams) error {
if strings.Contains(hostname, "rherror") {
return fmt.Errorf("failed to update regional hostname")
}
m.Actions = append(m.Actions, MockAction{
Name: "UpdateDataLocalizationRegionalHostname",
ZoneId: params.ZoneID.Value,
RecordId: "",
RegionalHostname: regionalHostname{
hostname: hostname,
regionKey: params.RegionKey.Value,
},
})
return nil
}
func (m *mockCloudFlareClient) DeleteDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameDeleteParams) error {
if strings.Contains(hostname, "rherror") {
return fmt.Errorf("failed to delete regional hostname")
}
m.Actions = append(m.Actions, MockAction{
Name: "DeleteDataLocalizationRegionalHostname",
ZoneId: params.ZoneID.Value,
RecordId: "",
RegionalHostname: regionalHostname{
hostname: hostname,
},
})
return nil
}
func TestCloudflareRegionalHostnameActions(t *testing.T) {
tests := []struct {
name string
records map[string]cloudflare.DNSRecord
regionalHostnames []regionalHostname
endpoints []*endpoint.Endpoint
want []MockAction
}{
{
name: "create",
records: map[string]cloudflare.DNSRecord{},
regionalHostnames: []regionalHostname{},
endpoints: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "create.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "eu",
},
},
},
},
want: []MockAction{
{
Name: "Create",
ZoneId: "001",
RecordId: generateDNSRecordID("A", "create.bar.com", "127.0.0.1"),
RecordData: cloudflare.DNSRecord{
ID: generateDNSRecordID("A", "create.bar.com", "127.0.0.1"),
Type: "A",
Name: "create.bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
{
Name: "CreateDataLocalizationRegionalHostname",
ZoneId: "001",
RegionalHostname: regionalHostname{
hostname: "create.bar.com",
regionKey: "eu",
},
},
},
},
{
name: "Update",
records: map[string]cloudflare.DNSRecord{
"update.bar.com": {
ID: generateDNSRecordID("A", "update.bar.com", "127.0.0.1"),
Type: "A",
Name: "update.bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
regionalHostnames: []regionalHostname{
{
hostname: "update.bar.com",
regionKey: "us",
},
},
endpoints: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "update.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "eu",
},
},
},
},
want: []MockAction{
{
Name: "Update",
ZoneId: "001",
RecordId: generateDNSRecordID("A", "update.bar.com", "127.0.0.1"),
RecordData: cloudflare.DNSRecord{
ID: generateDNSRecordID("A", "update.bar.com", "127.0.0.1"),
Type: "A",
Name: "update.bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
{
Name: "UpdateDataLocalizationRegionalHostname",
ZoneId: "001",
RegionalHostname: regionalHostname{
hostname: "update.bar.com",
regionKey: "eu",
},
},
},
},
{
name: "Delete",
records: map[string]cloudflare.DNSRecord{
"update.bar.com": {
ID: generateDNSRecordID("A", "delete.bar.com", "127.0.0.1"),
Type: "A",
Name: "delete.bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
regionalHostnames: []regionalHostname{
{
hostname: "delete.bar.com",
regionKey: "us",
},
},
endpoints: []*endpoint.Endpoint{},
want: []MockAction{
{
Name: "Delete",
ZoneId: "001",
RecordId: generateDNSRecordID("A", "delete.bar.com", "127.0.0.1"),
RecordData: cloudflare.DNSRecord{},
},
{
Name: "DeleteDataLocalizationRegionalHostname",
ZoneId: "001",
RegionalHostname: regionalHostname{
hostname: "delete.bar.com",
},
},
},
},
{
name: "No change",
records: map[string]cloudflare.DNSRecord{
"nochange.bar.com": {
ID: generateDNSRecordID("A", "nochange.bar.com", "127.0.0.1"),
Type: "A",
Name: "nochange.bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
regionalHostnames: []regionalHostname{
{
hostname: "nochange.bar.com",
regionKey: "eu",
},
},
endpoints: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "nochange.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "eu",
},
},
},
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := &CloudFlareProvider{
RegionalServicesConfig: RegionalServicesConfig{Enabled: true, RegionKey: "us"},
Client: &mockCloudFlareClient{
Zones: map[string]string{
"001": "bar.com",
},
Records: map[string]map[string]cloudflare.DNSRecord{
"001": tt.records,
},
regionalHostnames: map[string][]regionalHostname{
"001": tt.regionalHostnames,
},
},
}
AssertActions(t, provider, tt.endpoints, tt.want, []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME})
})
}
}
func TestCloudflareRegionalHostnameDefaults(t *testing.T) {
endpoints := []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "bar.com",
Targets: endpoint.Targets{"127.0.0.1", "127.0.0.2"},
},
}
AssertActions(t, &CloudFlareProvider{RegionalServicesConfig: RegionalServicesConfig{Enabled: true, RegionKey: "us"}}, endpoints, []MockAction{
{
Name: "Create",
ZoneId: "001",
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
RecordData: cloudflare.DNSRecord{
ID: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
Type: "A",
Name: "bar.com",
Content: "127.0.0.1",
TTL: 1,
Proxied: proxyDisabled,
},
},
{
Name: "Create",
ZoneId: "001",
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
RecordData: cloudflare.DNSRecord{
ID: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
Type: "A",
Name: "bar.com",
Content: "127.0.0.2",
TTL: 1,
Proxied: proxyDisabled,
},
},
{
Name: "CreateDataLocalizationRegionalHostname",
ZoneId: "001",
RegionalHostname: regionalHostname{
hostname: "bar.com",
regionKey: "us",
},
},
},
[]string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
)
}
func Test_regionalHostname(t *testing.T) {
type args struct {
endpoint *endpoint.Endpoint
config RegionalServicesConfig
}
tests := []struct {
name string
args args
want regionalHostname
}{
{
name: "no region key",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "A",
DNSName: "example.com",
},
config: RegionalServicesConfig{
Enabled: true,
RegionKey: "",
},
},
want: regionalHostname{
hostname: "example.com",
regionKey: "",
},
},
{
name: "default region key",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "A",
DNSName: "example.com",
},
config: RegionalServicesConfig{
Enabled: true,
RegionKey: "us",
},
},
want: regionalHostname{
hostname: "example.com",
regionKey: "us",
},
},
{
name: "endpoint with region key",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "A",
DNSName: "example.com",
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "eu",
},
},
},
config: RegionalServicesConfig{
Enabled: true,
RegionKey: "us",
},
},
want: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
name: "endpoint with empty region key",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "A",
DNSName: "example.com",
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "",
},
},
},
config: RegionalServicesConfig{
Enabled: true,
RegionKey: "us",
},
},
want: regionalHostname{
hostname: "example.com",
regionKey: "",
},
},
{
name: "unsupported record type",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "TXT",
DNSName: "example.com",
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "eu",
},
},
},
config: RegionalServicesConfig{
Enabled: true,
RegionKey: "us",
},
},
want: regionalHostname{},
},
{
name: "disabled",
args: args{
endpoint: &endpoint.Endpoint{
RecordType: "A",
DNSName: "example.com",
ProviderSpecific: endpoint.ProviderSpecific{
{
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
Value: "us",
},
},
},
config: RegionalServicesConfig{
Enabled: false,
},
},
want: regionalHostname{
hostname: "",
regionKey: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := CloudFlareProvider{RegionalServicesConfig: tt.args.config}
got := p.regionalHostname(tt.args.endpoint)
assert.Equal(t, tt.want, got)
})
}
}
func Test_desiredDataLocalizationRegionalHostnames(t *testing.T) {
tests := []struct {
name string
changes []*cloudFlareChange
want []regionalHostname
wantErr bool
}{
{
name: "empty input",
changes: []*cloudFlareChange{},
want: nil,
wantErr: false,
},
{
name: "change without regional hostname config",
changes: []*cloudFlareChange{{
Action: cloudFlareCreate,
}},
want: nil,
wantErr: false,
},
{
name: "changes with same hostname and region key",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
Action: cloudFlareUpdate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "eu",
},
},
wantErr: false,
},
{
name: "changes with same hostname but different region keys",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
Action: cloudFlareUpdate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "us", // Different region key
},
},
},
want: nil,
wantErr: true,
},
{
name: "changes with different hostnames",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example1.com",
regionKey: "eu",
},
},
{
Action: cloudFlareUpdate,
RegionalHostname: regionalHostname{
hostname: "example2.com",
regionKey: "us",
},
},
{
Action: cloudFlareDelete,
RegionalHostname: regionalHostname{
hostname: "example3.com",
regionKey: "us",
},
},
},
want: []regionalHostname{
{
hostname: "example1.com",
regionKey: "eu",
},
{
hostname: "example2.com",
regionKey: "us",
},
{
hostname: "example3.com",
regionKey: "",
},
},
wantErr: false,
},
{
name: "change with empty region key",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "", // Empty region key
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "",
},
},
wantErr: false,
},
{
name: "empty region key followed by region key",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "", // Empty region key
},
},
{
Action: cloudFlareUpdate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "eu",
},
},
wantErr: false,
},
{
name: "region key followed by empty region key",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
Action: cloudFlareUpdate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu", // Empty region key
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "eu",
},
},
wantErr: false,
},
{
name: "delete followed by create for the same hostname",
changes: []*cloudFlareChange{
{
Action: cloudFlareDelete,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "eu",
},
},
wantErr: false,
},
{
name: "create followed by delete for the same hostname",
changes: []*cloudFlareChange{
{
Action: cloudFlareCreate,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
{
Action: cloudFlareDelete,
RegionalHostname: regionalHostname{
hostname: "example.com",
regionKey: "eu",
},
},
},
want: []regionalHostname{
{
hostname: "example.com",
regionKey: "eu",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := desiredRegionalHostnames(tt.changes)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
sort.Slice(got, func(i, j int) bool {
return got[i].hostname < got[j].hostname
})
sort.Slice(tt.want, func(i, j int) bool {
return tt.want[i].hostname < tt.want[j].hostname
})
assert.Equal(t, tt.want, got)
})
}
}
func Test_dataLocalizationRegionalHostnamesChanges(t *testing.T) {
tests := []struct {
name string
desired []regionalHostname
regionalHostnames regionalHostnamesMap
want []regionalHostnameChange
}{
{
name: "empty desired and current lists",
desired: []regionalHostname{},
regionalHostnames: regionalHostnamesMap{},
want: []regionalHostnameChange{},
},
{
name: "multiple changes",
desired: []regionalHostname{
{
hostname: "create.example.com",
regionKey: "eu",
},
{
hostname: "update.example.com",
regionKey: "eu",
},
{
hostname: "delete.example.com",
regionKey: "",
},
{
hostname: "nochange.example.com",
regionKey: "us",
},
{
hostname: "absent.example.com",
regionKey: "",
},
},
regionalHostnames: regionalHostnamesMap{
"update.example.com": regionalHostname{
hostname: "update.example.com",
regionKey: "us",
},
"delete.example.com": regionalHostname{
hostname: "delete.example.com",
regionKey: "ap",
},
"nochange.example.com": regionalHostname{
hostname: "nochange.example.com",
regionKey: "us",
},
},
want: []regionalHostnameChange{
{
action: cloudFlareCreate,
regionalHostname: regionalHostname{
hostname: "create.example.com",
regionKey: "eu",
},
},
{
action: cloudFlareUpdate,
regionalHostname: regionalHostname{
hostname: "update.example.com",
regionKey: "eu",
},
},
{
action: cloudFlareDelete,
regionalHostname: regionalHostname{
hostname: "delete.example.com",
regionKey: "",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := regionalHostnamesChanges(tt.desired, tt.regionalHostnames)
assert.Equal(t, tt.want, got)
})
}
}
func TestRecordsWithListRegionalHostnameFaillure(t *testing.T) {
client := &mockCloudFlareClient{
Zones: map[string]string{
"rherror": "error.com",
},
Records: map[string]map[string]cloudflare.DNSRecord{
"rherror": {"foo.error.com": {Type: "A"}},
},
}
failingProvider := &CloudFlareProvider{
Client: client,
RegionalServicesConfig: RegionalServicesConfig{Enabled: true},
}
_, err := failingProvider.Records(t.Context())
assert.Error(t, err, "listing regional hostnames should fail")
}
func TestApplyChangesWithRegionalHostnamesFaillures(t *testing.T) {
t.Parallel()
type fields struct {
Records map[string]cloudflare.DNSRecord
RegionalHostnames []regionalHostname
RegionKey string
}
type args struct {
changes *plan.Changes
}
tests := []struct {
name string
fields fields
args args
errMsg string
expectDebug string
}{
{
name: "list zone fails",
args: args{
changes: &plan.Changes{
Create: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.error.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
},
},
errMsg: "failed to list regional hostnames",
},
{
name: "create fails",
fields: fields{
Records: map[string]cloudflare.DNSRecord{},
RegionalHostnames: []regionalHostname{},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
Create: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "rherror.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
},
},
expectDebug: "failed to create regional hostname",
},
{
name: "update fails",
fields: fields{
Records: map[string]cloudflare.DNSRecord{
"rherror.bar.com": {
ID: "123",
Type: "A",
Name: "rherror.bar.com",
Content: "127.0.0.1",
},
},
RegionalHostnames: []regionalHostname{
{hostname: "rherror.bar.com", regionKey: "us"},
},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
UpdateOld: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "rherror.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
UpdateNew: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "rherror.bar.com",
Targets: endpoint.Targets{"127.0.0.2"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
},
},
expectDebug: "failed to update regional hostname",
},
{
name: "delete fails",
fields: fields{
Records: map[string]cloudflare.DNSRecord{
"rherror.bar.com": {
ID: "123",
Type: "A",
Name: "newerror.bar.com",
Content: "127.0.0.1",
},
},
RegionalHostnames: []regionalHostname{
{hostname: "rherror.bar.com", regionKey: "us"},
},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
Delete: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "rherror.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
},
},
},
},
expectDebug: "failed to delete regional hostname",
},
{
// This should not happen in practice, but we test it to ensure we return an error.
name: "conflicting regional keys",
fields: fields{
Records: map[string]cloudflare.DNSRecord{},
RegionalHostnames: []regionalHostname{},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
Create: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "us"},
},
},
},
},
},
errMsg: "conflicting region keys",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
records := tt.fields.Records
if records == nil {
records = map[string]cloudflare.DNSRecord{}
}
p := &CloudFlareProvider{
Client: &mockCloudFlareClient{
Zones: map[string]string{
"001": "bar.com",
"rherror": "error.com",
},
Records: map[string]map[string]cloudflare.DNSRecord{
"001": records,
},
regionalHostnames: map[string][]regionalHostname{
"001": tt.fields.RegionalHostnames,
},
},
RegionalServicesConfig: RegionalServicesConfig{
Enabled: true,
RegionKey: tt.fields.RegionKey,
},
}
hook := testutils.LogsUnderTestWithLogLevel(log.DebugLevel, t)
err := p.ApplyChanges(t.Context(), tt.args.changes)
assert.Error(t, err, "ApplyChanges should return an error")
if tt.errMsg != "" && err != nil {
assert.Contains(t, err.Error(), tt.errMsg, "Expected error message to contain: %s", tt.errMsg)
}
if tt.expectDebug != "" {
testutils.TestHelperLogContains(tt.expectDebug, hook, t)
}
})
}
}
func TestApplyChangesWithRegionalHostnamesDryRun(t *testing.T) {
t.Parallel()
type fields struct {
Records map[string]cloudflare.DNSRecord
RegionalHostnames []regionalHostname
RegionKey string
}
type args struct {
changes *plan.Changes
}
tests := []struct {
name string
fields fields
args args
expectDebug string
}{
{
name: "create dry run",
fields: fields{
Records: map[string]cloudflare.DNSRecord{},
RegionalHostnames: []regionalHostname{},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
Create: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
},
},
expectDebug: "Dry run: skipping regional hostname change",
},
{
name: "update fails",
fields: fields{
Records: map[string]cloudflare.DNSRecord{
"foo.bar.com": {
ID: "123",
Type: "A",
Name: "foo.bar.com",
Content: "127.0.0.1",
},
},
RegionalHostnames: []regionalHostname{
{hostname: "foo.bar.com", regionKey: "us"},
},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
UpdateOld: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
UpdateNew: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.2"},
ProviderSpecific: endpoint.ProviderSpecific{
{Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key", Value: "eu"},
},
},
},
},
},
expectDebug: "Dry run: skipping regional hostname change",
},
{
name: "delete fails",
fields: fields{
Records: map[string]cloudflare.DNSRecord{
"foo.bar.com": {
ID: "123",
Type: "A",
Name: "foo.bar.com",
Content: "127.0.0.1",
},
},
RegionalHostnames: []regionalHostname{
{hostname: "foo.bar.com", regionKey: "us"},
},
RegionKey: "us",
},
args: args{
changes: &plan.Changes{
Delete: []*endpoint.Endpoint{
{
RecordType: "A",
DNSName: "foo.bar.com",
Targets: endpoint.Targets{"127.0.0.1"},
},
},
},
},
expectDebug: "Dry run: skipping regional hostname change",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
records := tt.fields.Records
if records == nil {
records = map[string]cloudflare.DNSRecord{}
}
p := &CloudFlareProvider{
DryRun: true,
Client: &mockCloudFlareClient{
Zones: map[string]string{
"001": "bar.com",
},
Records: map[string]map[string]cloudflare.DNSRecord{
"001": records,
},
regionalHostnames: map[string][]regionalHostname{
"001": tt.fields.RegionalHostnames,
},
},
RegionalServicesConfig: RegionalServicesConfig{
Enabled: true,
RegionKey: tt.fields.RegionKey,
},
}
hook := testutils.LogsUnderTestWithLogLevel(log.DebugLevel, t)
err := p.ApplyChanges(t.Context(), tt.args.changes)
assert.NoError(t, err, "ApplyChanges should not fail")
if tt.expectDebug != "" {
testutils.TestHelperLogContains(tt.expectDebug, hook, t)
}
})
}
}