mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
1189 lines
28 KiB
Go
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{
|
|
Update: []*plan.Update{
|
|
{
|
|
Old: &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"},
|
|
},
|
|
},
|
|
New: &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{
|
|
Update: []*plan.Update{
|
|
{
|
|
Old: &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"},
|
|
},
|
|
},
|
|
New: &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)
|
|
}
|
|
})
|
|
}
|
|
}
|