mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
feat(cloudflare): add support for MX records (#5283)
* feat(cloudflare): add support for MX records Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * test(txt): add additional TXT and MX record test cases Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * feat(endpoint): implement parsing for MX and SRV records with structured targets Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(txt): remove TXT record type from supported types in NewTXTRegistry Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(digitalocean): streamline MX record handling Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(cloudflare): improve error handling in change creation Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(endpoint): return all parsed SRV targets instead of a single target Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * test(endpoint): add parsing tests for MX and SRV records Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(endpoint): streamline MX and SRV record validation and parsing Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(digital_ocean): simplify MX record parsing Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(docs): update link to CRD source in MX record documentation Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(cloudflare): improve error handling for MX record parsing Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(cloudflare): improve error message formatting for MX record parsing Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(endpoint): rename ParseMXRecord to NewMXTarget and update references Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(endpoint): update NewMXTarget to return pointer and adjust tests accordingly Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(cloudflare): consolidate proxyEnabled and proxyDisabled variable declarations Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(endpoint): update TestNewMXTarget to reflect changes in MXTarget struct fields and add missing test case for host validation Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * fix(digitalocean): improve MX record handling by adjusting error handling and ensuring proper priority and host retrieval Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(endpoint): change MXTarget fields to unexported and update NewMXTarget to use them Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(cloudflare): update groupByNameAndTypeWithCustomHostnames to use provider methods and enhance MX record handling in tests Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * test(cloudflare): enhance test cover Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(endpoint): remove unused SRVTarget struct from endpoint.go Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * refactor(endpoint): rename NewMXTarget to NewMXRecord for clarity and update references Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> * Update docs/sources/mx-record.md Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> --------- Signed-off-by: Arthur Le Roux <arthurleroux@protonmail.com> Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
This commit is contained in:
parent
e324da8a43
commit
e17b5ed07b
@ -1,7 +1,7 @@
|
||||
# MX record with CRD source
|
||||
|
||||
You can create and manage MX records with the help of [CRD source](../sources/crd.md)
|
||||
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, `google` and `digitalocean` providers.
|
||||
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, `cloudflare`, `digitalocean` and `google` providers.
|
||||
|
||||
In order to start managing MX records you need to set the `--managed-record-types=MX` flag.
|
||||
|
||||
|
@ -71,6 +71,12 @@ func (ttl TTL) IsConfigured() bool {
|
||||
// Targets is a representation of a list of targets for an endpoint.
|
||||
type Targets []string
|
||||
|
||||
// MXTarget represents a single MX (Mail Exchange) record target, including its priority and host.
|
||||
type MXTarget struct {
|
||||
priority uint16
|
||||
host string
|
||||
}
|
||||
|
||||
// NewTargets is a convenience method to create a new Targets object from a vararg of strings
|
||||
func NewTargets(target ...string) Targets {
|
||||
t := make(Targets, 0, len(target))
|
||||
@ -394,22 +400,44 @@ func (e *Endpoint) CheckEndpoint() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewMXRecord parses a string representation of an MX record target (e.g., "10 mail.example.com")
|
||||
// and returns an MXTarget struct. Returns an error if the input is invalid.
|
||||
func NewMXRecord(target string) (*MXTarget, error) {
|
||||
parts := strings.Fields(strings.TrimSpace(target))
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid MX record target: %s. MX records must have a preference value and a host, e.g. '10 example.com'", target)
|
||||
}
|
||||
|
||||
priority, err := strconv.ParseUint(parts[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid integer value in target: %s", target)
|
||||
}
|
||||
|
||||
return &MXTarget{
|
||||
priority: uint16(priority),
|
||||
host: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPriority returns the priority of the MX record target.
|
||||
func (m *MXTarget) GetPriority() *uint16 {
|
||||
return &m.priority
|
||||
}
|
||||
|
||||
// GetHost returns the host of the MX record target.
|
||||
func (m *MXTarget) GetHost() *string {
|
||||
return &m.host
|
||||
}
|
||||
|
||||
func (t Targets) ValidateMXRecord() bool {
|
||||
for _, target := range t {
|
||||
// MX records must have a preference value to indicate priority, e.g. "10 example.com"
|
||||
// as per https://www.rfc-editor.org/rfc/rfc974.txt
|
||||
targetParts := strings.Fields(strings.TrimSpace(target))
|
||||
if len(targetParts) != 2 {
|
||||
log.Debugf("Invalid MX record target: %s. MX records must have a preference value to indicate priority, e.g. '10 example.com'", target)
|
||||
return false
|
||||
}
|
||||
preferenceRaw := targetParts[0]
|
||||
_, err := strconv.ParseUint(preferenceRaw, 10, 16)
|
||||
_, err := NewMXRecord(target)
|
||||
if err != nil {
|
||||
log.Debugf("Invalid SRV record target: %s. Invalid integer value in target.", target)
|
||||
log.Debugf("Invalid MX record target: %s. %v", target, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -815,3 +815,113 @@ func TestPDNScheckEndpoint(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMXTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
target string
|
||||
expected *MXTarget
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
description: "Valid MX record",
|
||||
target: "10 example.com",
|
||||
expected: &MXTarget{priority: 10, host: "example.com"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "Invalid MX record with missing priority",
|
||||
target: "example.com",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "Invalid MX record with non-integer priority",
|
||||
target: "abc example.com",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "Invalid MX record with too many parts",
|
||||
target: "10 example.com extra",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "Missing host",
|
||||
target: "10 ",
|
||||
expected: nil,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
actual, err := NewMXRecord(tt.target)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEndpoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
endpoint Endpoint
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
description: "Valid MX record target",
|
||||
endpoint: Endpoint{
|
||||
DNSName: "example.com",
|
||||
RecordType: RecordTypeMX,
|
||||
Targets: Targets{"10 example.com"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: "Invalid MX record target",
|
||||
endpoint: Endpoint{
|
||||
DNSName: "example.com",
|
||||
RecordType: RecordTypeMX,
|
||||
Targets: Targets{"example.com"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: "Valid SRV record target",
|
||||
endpoint: Endpoint{
|
||||
DNSName: "_service._tcp.example.com",
|
||||
RecordType: RecordTypeSRV,
|
||||
Targets: Targets{"10 5 5060 example.com"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: "Invalid SRV record target",
|
||||
endpoint: Endpoint{
|
||||
DNSName: "_service._tcp.example.com",
|
||||
RecordType: RecordTypeSRV,
|
||||
Targets: Targets{"10 5 example.com"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: "Non-MX/SRV record type",
|
||||
endpoint: Endpoint{
|
||||
DNSName: "example.com",
|
||||
RecordType: RecordTypeA,
|
||||
Targets: Targets{"192.168.1.1"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
actual := tt.endpoint.CheckEndpoint()
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -65,16 +65,6 @@ func (action changeAction) String() string {
|
||||
return changeActionNames[action]
|
||||
}
|
||||
|
||||
// We have to use pointers to bools now, as the upstream cloudflare-go library requires them
|
||||
// see: https://github.com/cloudflare/cloudflare-go/pull/595
|
||||
|
||||
var (
|
||||
// proxyEnabled is a pointer to a bool true showing the record should be proxied through cloudflare
|
||||
proxyEnabled *bool = boolPtr(true)
|
||||
// proxyDisabled is a pointer to a bool false showing the record should not be proxied through cloudflare
|
||||
proxyDisabled *bool = boolPtr(false)
|
||||
)
|
||||
|
||||
type DNSRecordIndex struct {
|
||||
Name string
|
||||
Type string
|
||||
@ -226,7 +216,7 @@ type CloudFlareProvider struct {
|
||||
RegionalServicesConfig RegionalServicesConfig
|
||||
}
|
||||
|
||||
// cloudFlareChange differentiates between ChangActions
|
||||
// cloudFlareChange differentiates between ChangeActions
|
||||
type cloudFlareChange struct {
|
||||
Action changeAction
|
||||
ResourceRecord cloudflare.DNSRecord
|
||||
@ -242,24 +232,30 @@ type RecordParamsTypes interface {
|
||||
|
||||
// updateDNSRecordParam is a function that returns the appropriate Record Param based on the cloudFlareChange passed in
|
||||
func updateDNSRecordParam(cfc cloudFlareChange) cloudflare.UpdateDNSRecordParams {
|
||||
return cloudflare.UpdateDNSRecordParams{
|
||||
Name: cfc.ResourceRecord.Name,
|
||||
TTL: cfc.ResourceRecord.TTL,
|
||||
Proxied: cfc.ResourceRecord.Proxied,
|
||||
Type: cfc.ResourceRecord.Type,
|
||||
Content: cfc.ResourceRecord.Content,
|
||||
params := cloudflare.UpdateDNSRecordParams{
|
||||
Name: cfc.ResourceRecord.Name,
|
||||
TTL: cfc.ResourceRecord.TTL,
|
||||
Proxied: cfc.ResourceRecord.Proxied,
|
||||
Type: cfc.ResourceRecord.Type,
|
||||
Content: cfc.ResourceRecord.Content,
|
||||
Priority: cfc.ResourceRecord.Priority,
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
// getCreateDNSRecordParam is a function that returns the appropriate Record Param based on the cloudFlareChange passed in
|
||||
func getCreateDNSRecordParam(cfc cloudFlareChange) cloudflare.CreateDNSRecordParams {
|
||||
return cloudflare.CreateDNSRecordParams{
|
||||
Name: cfc.ResourceRecord.Name,
|
||||
TTL: cfc.ResourceRecord.TTL,
|
||||
Proxied: cfc.ResourceRecord.Proxied,
|
||||
Type: cfc.ResourceRecord.Type,
|
||||
Content: cfc.ResourceRecord.Content,
|
||||
params := cloudflare.CreateDNSRecordParams{
|
||||
Name: cfc.ResourceRecord.Name,
|
||||
TTL: cfc.ResourceRecord.TTL,
|
||||
Proxied: cfc.ResourceRecord.Proxied,
|
||||
Type: cfc.ResourceRecord.Type,
|
||||
Content: cfc.ResourceRecord.Content,
|
||||
Priority: cfc.ResourceRecord.Priority,
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
func convertCloudflareError(err error) error {
|
||||
@ -392,7 +388,7 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
// As CloudFlare does not support "sets" of targets, but instead returns
|
||||
// a single entry for each name/type/target, we have to group by name
|
||||
// and record to allow the planner to calculate the correct plan. See #992.
|
||||
zoneEndpoints := groupByNameAndTypeWithCustomHostnames(records, chs)
|
||||
zoneEndpoints := p.groupByNameAndTypeWithCustomHostnames(records, chs)
|
||||
|
||||
if err := p.addEnpointsProviderSpecificRegionKeyProperty(ctx, zone.ID, zoneEndpoints); err != nil {
|
||||
return nil, err
|
||||
@ -412,14 +408,24 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha
|
||||
if p.CustomHostnamesConfig.Enabled {
|
||||
for _, e := range changes.Delete {
|
||||
for _, target := range e.Targets {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareDelete, e, target, nil))
|
||||
change, err := p.newCloudFlareChange(cloudFlareDelete, e, target, nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range changes.Create {
|
||||
for _, target := range e.Targets {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareCreate, e, target, nil))
|
||||
change, err := p.newCloudFlareChange(cloudFlareCreate, e, target, nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,15 +435,30 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha
|
||||
add, remove, leave := provider.Difference(current.Targets, desired.Targets)
|
||||
|
||||
for _, a := range remove {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareDelete, current, a, current))
|
||||
change, err := p.newCloudFlareChange(cloudFlareDelete, current, a, current)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
|
||||
for _, a := range add {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareCreate, desired, a, current))
|
||||
change, err := p.newCloudFlareChange(cloudFlareCreate, desired, a, current)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
|
||||
for _, a := range leave {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareUpdate, desired, a, current))
|
||||
change, err := p.newCloudFlareChange(cloudFlareUpdate, desired, a, current)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,7 +466,12 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha
|
||||
if !p.CustomHostnamesConfig.Enabled {
|
||||
for _, e := range changes.Delete {
|
||||
for _, target := range e.Targets {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareDelete, e, target, nil))
|
||||
change, err := p.newCloudFlareChange(cloudFlareDelete, e, target, nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create cloudflare change: %v", err)
|
||||
continue
|
||||
}
|
||||
cloudflareChanges = append(cloudflareChanges, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -723,7 +749,7 @@ func (p *CloudFlareProvider) newCustomHostname(customHostname string, origin str
|
||||
}
|
||||
}
|
||||
|
||||
func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ep *endpoint.Endpoint, target string, current *endpoint.Endpoint) *cloudFlareChange {
|
||||
func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ep *endpoint.Endpoint, target string, current *endpoint.Endpoint) (*cloudFlareChange, error) {
|
||||
ttl := defaultTTL
|
||||
proxied := shouldBeProxied(ep, p.proxiedByDefault)
|
||||
|
||||
@ -753,6 +779,17 @@ func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ep *endpoi
|
||||
comment = p.DNSRecordsConfig.trimAndValidateComment(ep.DNSName, comment, p.ZoneHasPaidPlan)
|
||||
}
|
||||
|
||||
priority := (*uint16)(nil)
|
||||
if ep.RecordType == "MX" {
|
||||
mxRecord, err := endpoint.NewMXRecord(target)
|
||||
if err != nil {
|
||||
return &cloudFlareChange{}, fmt.Errorf("failed to parse MX record target %q: %w", target, err)
|
||||
} else {
|
||||
priority = mxRecord.GetPriority()
|
||||
target = *mxRecord.GetHost()
|
||||
}
|
||||
}
|
||||
|
||||
return &cloudFlareChange{
|
||||
Action: action,
|
||||
ResourceRecord: cloudflare.DNSRecord{
|
||||
@ -760,15 +797,16 @@ func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ep *endpoi
|
||||
TTL: ttl,
|
||||
// We have to use pointers to bools now, as the upstream cloudflare-go library requires them
|
||||
// see: https://github.com/cloudflare/cloudflare-go/pull/595
|
||||
Proxied: &proxied,
|
||||
Type: ep.RecordType,
|
||||
Content: target,
|
||||
Comment: comment,
|
||||
Proxied: &proxied,
|
||||
Type: ep.RecordType,
|
||||
Content: target,
|
||||
Comment: comment,
|
||||
Priority: priority,
|
||||
},
|
||||
RegionalHostname: p.regionalHostname(ep),
|
||||
CustomHostnamesPrev: prevCustomHostnames,
|
||||
CustomHostnames: newCustomHostnames,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newDNSRecordIndex(r cloudflare.DNSRecord) DNSRecordIndex {
|
||||
@ -877,14 +915,14 @@ func getEndpointCustomHostnames(ep *endpoint.Endpoint) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHostnamesMap) []*endpoint.Endpoint {
|
||||
func (p *CloudFlareProvider) groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHostnamesMap) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
// group supported records by name and type
|
||||
groups := map[string][]cloudflare.DNSRecord{}
|
||||
|
||||
for _, r := range records {
|
||||
if !provider.SupportedRecordType(r.Type) {
|
||||
if !p.SupportedAdditionalRecordTypes(r.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -910,7 +948,11 @@ func groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHost
|
||||
}
|
||||
targets := make([]string, len(records))
|
||||
for i, record := range records {
|
||||
targets[i] = record.Content
|
||||
if records[i].Type == "MX" {
|
||||
targets[i] = fmt.Sprintf("%v %v", *record.Priority, record.Content)
|
||||
} else {
|
||||
targets[i] = record.Content
|
||||
}
|
||||
}
|
||||
e := endpoint.NewEndpointWithTTL(
|
||||
records[0].Name,
|
||||
@ -937,12 +979,15 @@ func groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHost
|
||||
|
||||
endpoints = append(endpoints, e)
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
// boolPtr is used as a helper function to return a pointer to a boolean
|
||||
// Needed because some parameters require a pointer.
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
// SupportedRecordType returns true if the record type is supported by the provider
|
||||
func (p *CloudFlareProvider) SupportedAdditionalRecordTypes(recordType string) bool {
|
||||
switch recordType {
|
||||
case endpoint.RecordTypeMX:
|
||||
return true
|
||||
default:
|
||||
return provider.SupportedRecordType(recordType)
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,12 @@ import (
|
||||
"sigs.k8s.io/external-dns/source/annotations"
|
||||
)
|
||||
|
||||
// proxyEnabled and proxyDisabled are pointers to bool values used to set if a record should be proxied through Cloudflare.
|
||||
var (
|
||||
proxyEnabled *bool = testutils.ToPtr(true)
|
||||
proxyDisabled *bool = testutils.ToPtr(false)
|
||||
)
|
||||
|
||||
type MockAction struct {
|
||||
Name string
|
||||
ZoneId string
|
||||
@ -117,7 +123,7 @@ func NewMockCloudFlareClientWithRecords(records map[string][]cloudflare.DNSRecor
|
||||
func getDNSRecordFromRecordParams(rp any) cloudflare.DNSRecord {
|
||||
switch params := rp.(type) {
|
||||
case cloudflare.CreateDNSRecordParams:
|
||||
return cloudflare.DNSRecord{
|
||||
record := cloudflare.DNSRecord{
|
||||
ID: params.ID,
|
||||
Name: params.Name,
|
||||
TTL: params.TTL,
|
||||
@ -125,8 +131,12 @@ func getDNSRecordFromRecordParams(rp any) cloudflare.DNSRecord {
|
||||
Type: params.Type,
|
||||
Content: params.Content,
|
||||
}
|
||||
if params.Type == "MX" {
|
||||
record.Priority = params.Priority
|
||||
}
|
||||
return record
|
||||
case cloudflare.UpdateDNSRecordParams:
|
||||
return cloudflare.DNSRecord{
|
||||
record := cloudflare.DNSRecord{
|
||||
ID: params.ID,
|
||||
Name: params.Name,
|
||||
TTL: params.TTL,
|
||||
@ -134,6 +144,10 @@ func getDNSRecordFromRecordParams(rp any) cloudflare.DNSRecord {
|
||||
Type: params.Type,
|
||||
Content: params.Content,
|
||||
}
|
||||
if params.Type == "MX" {
|
||||
record.Priority = params.Priority
|
||||
}
|
||||
return record
|
||||
default:
|
||||
return cloudflare.DNSRecord{}
|
||||
}
|
||||
@ -413,7 +427,7 @@ func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endp
|
||||
|
||||
// Records other than A, CNAME and NS are not supported by planner, just create them
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.RecordType != "A" && endpoint.RecordType != "CNAME" && endpoint.RecordType != "NS" {
|
||||
if !slices.Contains(managedRecords, endpoint.RecordType) {
|
||||
changes.Create = append(changes.Create, endpoint)
|
||||
}
|
||||
}
|
||||
@ -508,6 +522,77 @@ func TestCloudflareCname(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestCloudflareMx(t *testing.T) {
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
RecordType: "MX",
|
||||
DNSName: "mx.bar.com",
|
||||
Targets: endpoint.Targets{"10 google.com", "20 facebook.com"},
|
||||
},
|
||||
}
|
||||
|
||||
AssertActions(t, &CloudFlareProvider{}, endpoints, []MockAction{
|
||||
{
|
||||
Name: "Create",
|
||||
ZoneId: "001",
|
||||
RecordId: generateDNSRecordID("MX", "mx.bar.com", "google.com"),
|
||||
RecordData: cloudflare.DNSRecord{
|
||||
ID: generateDNSRecordID("MX", "mx.bar.com", "google.com"),
|
||||
Type: "MX",
|
||||
Name: "mx.bar.com",
|
||||
Content: "google.com",
|
||||
Priority: cloudflare.Uint16Ptr(10),
|
||||
TTL: 1,
|
||||
Proxied: proxyDisabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create",
|
||||
ZoneId: "001",
|
||||
RecordId: generateDNSRecordID("MX", "mx.bar.com", "facebook.com"),
|
||||
RecordData: cloudflare.DNSRecord{
|
||||
ID: generateDNSRecordID("MX", "mx.bar.com", "facebook.com"),
|
||||
Type: "MX",
|
||||
Name: "mx.bar.com",
|
||||
Content: "facebook.com",
|
||||
Priority: cloudflare.Uint16Ptr(20),
|
||||
TTL: 1,
|
||||
Proxied: proxyDisabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{endpoint.RecordTypeMX},
|
||||
)
|
||||
}
|
||||
|
||||
func TestCloudflareTxt(t *testing.T) {
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
RecordType: "TXT",
|
||||
DNSName: "txt.bar.com",
|
||||
Targets: endpoint.Targets{"v=spf1 include:_spf.google.com ~all"},
|
||||
},
|
||||
}
|
||||
|
||||
AssertActions(t, &CloudFlareProvider{}, endpoints, []MockAction{
|
||||
{
|
||||
Name: "Create",
|
||||
ZoneId: "001",
|
||||
RecordId: generateDNSRecordID("TXT", "txt.bar.com", "v=spf1 include:_spf.google.com ~all"),
|
||||
RecordData: cloudflare.DNSRecord{
|
||||
ID: generateDNSRecordID("TXT", "txt.bar.com", "v=spf1 include:_spf.google.com ~all"),
|
||||
Type: "TXT",
|
||||
Name: "txt.bar.com",
|
||||
Content: "v=spf1 include:_spf.google.com ~all",
|
||||
TTL: 1,
|
||||
Proxied: proxyDisabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{endpoint.RecordTypeTXT},
|
||||
)
|
||||
}
|
||||
|
||||
func TestCloudflareCustomTTL(t *testing.T) {
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
@ -687,12 +772,24 @@ func TestCloudflareSetProxied(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
target := "127.0.0.1"
|
||||
var targets endpoint.Targets
|
||||
var content string
|
||||
var priority *uint16
|
||||
|
||||
if testCase.recordType == "MX" {
|
||||
targets = endpoint.Targets{"10 mx.example.com"}
|
||||
content = "mx.example.com"
|
||||
priority = cloudflare.Uint16Ptr(10)
|
||||
} else {
|
||||
targets = endpoint.Targets{"127.0.0.1"}
|
||||
content = "127.0.0.1"
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
RecordType: testCase.recordType,
|
||||
DNSName: testCase.domain,
|
||||
Targets: endpoint.Targets{target},
|
||||
Targets: endpoint.Targets{targets[0]},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
endpoint.ProviderSpecificProperty{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
@ -701,22 +798,26 @@ func TestCloudflareSetProxied(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedID := fmt.Sprintf("%s-%s-%s", testCase.domain, testCase.recordType, target)
|
||||
expectedID := fmt.Sprintf("%s-%s-%s", testCase.domain, testCase.recordType, content)
|
||||
recordData := cloudflare.DNSRecord{
|
||||
ID: expectedID,
|
||||
Type: testCase.recordType,
|
||||
Name: testCase.domain,
|
||||
Content: content,
|
||||
TTL: 1,
|
||||
Proxied: testCase.proxiable,
|
||||
}
|
||||
if testCase.recordType == "MX" {
|
||||
recordData.Priority = priority
|
||||
}
|
||||
AssertActions(t, &CloudFlareProvider{}, endpoints, []MockAction{
|
||||
{
|
||||
Name: "Create",
|
||||
ZoneId: "001",
|
||||
RecordId: expectedID,
|
||||
RecordData: cloudflare.DNSRecord{
|
||||
ID: expectedID,
|
||||
Type: testCase.recordType,
|
||||
Name: testCase.domain,
|
||||
Content: "127.0.0.1",
|
||||
TTL: 1,
|
||||
Proxied: testCase.proxiable,
|
||||
},
|
||||
Name: "Create",
|
||||
ZoneId: "001",
|
||||
RecordId: expectedID,
|
||||
RecordData: recordData,
|
||||
},
|
||||
}, []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS}, testCase.recordType+" record on "+testCase.domain)
|
||||
}, []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS, endpoint.RecordTypeMX}, testCase.recordType+" record on "+testCase.domain)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1125,6 +1226,11 @@ func TestCloudflareGetRecordID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCloudflareGroupByNameAndType(t *testing.T) {
|
||||
provider := &CloudFlareProvider{
|
||||
Client: NewMockCloudFlareClient(),
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"bar.com"}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
}
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Records []cloudflare.DNSRecord
|
||||
@ -1359,7 +1465,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
|
||||
for _, r := range tc.Records {
|
||||
records[newDNSRecordIndex(r)] = r
|
||||
}
|
||||
endpoints := groupByNameAndTypeWithCustomHostnames(records, CustomHostnamesMap{})
|
||||
endpoints := provider.groupByNameAndTypeWithCustomHostnames(records, CustomHostnamesMap{})
|
||||
// Targets order could be random with underlying map
|
||||
for _, ep := range endpoints {
|
||||
slices.Sort(ep.Targets)
|
||||
@ -1371,6 +1477,44 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupByNameAndTypeWithCustomHostnames_MX(t *testing.T) {
|
||||
client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{
|
||||
"001": {
|
||||
{
|
||||
ID: "mx-1",
|
||||
Name: "mx.bar.com",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
TTL: 3600,
|
||||
Content: "mail.bar.com",
|
||||
Priority: cloudflare.Uint16Ptr(10),
|
||||
},
|
||||
{
|
||||
ID: "mx-2",
|
||||
Name: "mx.bar.com",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
TTL: 3600,
|
||||
Content: "mail2.bar.com",
|
||||
Priority: cloudflare.Uint16Ptr(20),
|
||||
},
|
||||
},
|
||||
})
|
||||
provider := &CloudFlareProvider{
|
||||
Client: client,
|
||||
}
|
||||
ctx := context.Background()
|
||||
chs := CustomHostnamesMap{}
|
||||
records, err := provider.listDNSRecordsWithAutoPagination(ctx, "001")
|
||||
assert.NoError(t, err)
|
||||
|
||||
endpoints := provider.groupByNameAndTypeWithCustomHostnames(records, chs)
|
||||
assert.Len(t, endpoints, 1)
|
||||
mxEndpoint := endpoints[0]
|
||||
assert.Equal(t, "mx.bar.com", mxEndpoint.DNSName)
|
||||
assert.Equal(t, endpoint.RecordTypeMX, mxEndpoint.RecordType)
|
||||
assert.ElementsMatch(t, []string{"10 mail.bar.com", "20 mail2.bar.com"}, mxEndpoint.Targets)
|
||||
assert.Equal(t, endpoint.TTL(3600), mxEndpoint.RecordTTL)
|
||||
}
|
||||
|
||||
func TestProviderPropertiesIdempotency(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
@ -1650,7 +1794,7 @@ func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
||||
Targets: []string{"192.0.2.1"},
|
||||
}
|
||||
|
||||
change := p.newCloudFlareChange(cloudFlareCreate, ep, ep.Targets[0], nil)
|
||||
change, _ := p.newCloudFlareChange(cloudFlareCreate, ep, ep.Targets[0], nil)
|
||||
if change.RegionalHostname.RegionKey != "us" {
|
||||
t.Errorf("expected region key to be 'us', but got '%s'", change.RegionalHostname.RegionKey)
|
||||
}
|
||||
@ -1762,7 +1906,8 @@ func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
||||
|
||||
for _, test := range commentTestCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
change := test.provider.newCloudFlareChange(cloudFlareCreate, test.endpoint, test.endpoint.Targets[0], nil)
|
||||
change, err := test.provider.newCloudFlareChange(cloudFlareCreate, test.endpoint, test.endpoint.Targets[0], nil)
|
||||
assert.NoError(t, err)
|
||||
if len(change.ResourceRecord.Comment) != test.expected {
|
||||
t.Errorf("expected comment to be %d characters long, but got %d", test.expected, len(change.ResourceRecord.Comment))
|
||||
}
|
||||
@ -2077,420 +2222,12 @@ func TestCloudflareCustomHostnameOperations(t *testing.T) {
|
||||
domainFilter := endpoint.NewDomainFilter([]string{"bar.com"})
|
||||
|
||||
testFailCases := []struct {
|
||||
Name string
|
||||
Endpoints []*endpoint.Endpoint
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
Name: "failing to create custom hostname on record creation",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-create.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "same custom hostname to the same origin",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "origin.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4", "2.3.4.5"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "custom.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "another-origin.foo.bar.com",
|
||||
Targets: endpoint.Targets{"3.4.5.6"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "custom.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "create CNAME records with custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "c.foo.bar.com",
|
||||
Targets: endpoint.Targets{"c.cname.foo.bar.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "c.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "TXT registry record should not attempt to create custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "cname-c.foo.bar.com",
|
||||
Targets: endpoint.Targets{
|
||||
"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/external-dns/my-domain-here-app",
|
||||
},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "c.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "failing to update custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fail.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-create.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "adding failing to list custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fail.list.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-list-1.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "causing to list failing to list custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "adding normal custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "b.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "b.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "updating to erroring custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "b.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-create.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "set to custom hostname which would error on removing",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "b.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-delete.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "delete erroring on remove custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "b.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "create erroring to remove custom hostname on record deletion",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "b.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "newerror-delete.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
Name: "failing to remove custom hostname on record deletion",
|
||||
Endpoints: []*endpoint.Endpoint{},
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Endpoints []*endpoint.Endpoint
|
||||
ExpectedCustomHostnames map[string]string
|
||||
}{
|
||||
{
|
||||
Name: "add A record without custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "nocustomhostname.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{},
|
||||
},
|
||||
{
|
||||
Name: "add custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "a.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "txt.foo.bar.com",
|
||||
Targets: endpoint.Targets{"value"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "txt.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{
|
||||
"a.foo.fancybar.com": "a.foo.bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update custom hostname",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "a2.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{
|
||||
"a2.foo.fancybar.com": "a.foo.bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "add another unsorted custom hostnames",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "a3.foo.fancybar.com,a4.foo.fancybar.com,a2.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{
|
||||
"a2.foo.fancybar.com": "a.foo.bar.com",
|
||||
"a3.foo.fancybar.com": "a.foo.bar.com",
|
||||
"a4.foo.fancybar.com": "a.foo.bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rename custom hostnames",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "a3.foo.fancybar.com,a44.foo.fancybar.com,a22.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{
|
||||
"a22.foo.fancybar.com": "a.foo.bar.com",
|
||||
"a3.foo.fancybar.com": "a.foo.bar.com",
|
||||
"a44.foo.fancybar.com": "a.foo.bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "remove some custom hostnames",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "a3.foo.fancybar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{
|
||||
"a3.foo.fancybar.com": "a.foo.bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "delete custom hostnames",
|
||||
Endpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.foo.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
},
|
||||
},
|
||||
ExpectedCustomHostnames: map[string]string{},
|
||||
},
|
||||
}
|
||||
}{}
|
||||
|
||||
for _, tc := range testFailCases {
|
||||
var err error
|
||||
var records, endpoints []*endpoint.Endpoint
|
||||
|
||||
records, err = provider.Records(ctx)
|
||||
if errors.Is(err, nil) {
|
||||
endpoints, err = provider.AdjustEndpoints(tc.Endpoints)
|
||||
}
|
||||
if errors.Is(err, nil) {
|
||||
plan := &plan.Plan{
|
||||
Current: records,
|
||||
Desired: endpoints,
|
||||
DomainFilter: endpoint.MatchAllDomainFilters{domainFilter},
|
||||
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeTXT},
|
||||
}
|
||||
planned := plan.Calculate()
|
||||
err = provider.ApplyChanges(context.Background(), planned.Changes)
|
||||
|
||||
}
|
||||
if e := checkFailed(tc.Name, err, tc.shouldFail); !errors.Is(e, nil) {
|
||||
t.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
records, err := provider.Records(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %v", err)
|
||||
@ -2522,6 +2259,9 @@ func TestCloudflareCustomHostnameOperations(t *testing.T) {
|
||||
for _, ch := range chs {
|
||||
actualCustomHostnames[ch.Hostname] = ch.CustomOriginServer
|
||||
}
|
||||
if len(actualCustomHostnames) == 0 {
|
||||
actualCustomHostnames = nil
|
||||
}
|
||||
assert.Equal(t, tc.ExpectedCustomHostnames, actualCustomHostnames, "custom hostnames should be the same")
|
||||
}
|
||||
}
|
||||
@ -2852,3 +2592,171 @@ func TestZoneHasPaidPlan(t *testing.T) {
|
||||
}
|
||||
assert.False(t, cfproviderWithZoneError.ZoneHasPaidPlan("subdomain.foo.com"))
|
||||
}
|
||||
func TestCloudflareApplyChanges_AllErrorLogPaths(t *testing.T) {
|
||||
hook := testutils.LogsUnderTestWithLogLevel(log.ErrorLevel, t)
|
||||
|
||||
client := NewMockCloudFlareClient()
|
||||
provider := &CloudFlareProvider{
|
||||
Client: client,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
changes *plan.Changes
|
||||
customHostnamesEnabled bool
|
||||
errorLogCount int
|
||||
}{
|
||||
{
|
||||
name: "Create error (custom hostnames enabled)",
|
||||
changes: &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-create.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-create-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
customHostnamesEnabled: true,
|
||||
errorLogCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Delete error (custom hostnames enabled)",
|
||||
changes: &plan.Changes{
|
||||
Delete: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-delete.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-delete-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
customHostnamesEnabled: true,
|
||||
errorLogCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Update add/remove error (custom hostnames enabled)",
|
||||
changes: &plan.Changes{
|
||||
UpdateNew: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-update-add.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-update-add-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
UpdateOld: []*endpoint.Endpoint{{
|
||||
DNSName: "old-bad-update-add.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx-but-still-updated"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-update-add-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
customHostnamesEnabled: true,
|
||||
errorLogCount: 2,
|
||||
},
|
||||
{
|
||||
name: "Update leave error (custom hostnames enabled)",
|
||||
changes: &plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-update-leave.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-update-leave-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
UpdateNew: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-update-leave.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname",
|
||||
Value: "bad-update-leave-custom.bar.com",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
customHostnamesEnabled: true,
|
||||
errorLogCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Delete error (custom hostnames disabled)",
|
||||
changes: &plan.Changes{
|
||||
Delete: []*endpoint.Endpoint{{
|
||||
DNSName: "bad-delete2.bar.com",
|
||||
RecordType: "MX",
|
||||
Targets: endpoint.Targets{"not-a-valid-mx"},
|
||||
}},
|
||||
},
|
||||
customHostnamesEnabled: false,
|
||||
errorLogCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// Test with custom hostnames enabled and disabled
|
||||
for _, tc := range cases {
|
||||
if tc.customHostnamesEnabled {
|
||||
provider.CustomHostnamesConfig = CustomHostnamesConfig{Enabled: true}
|
||||
} else {
|
||||
provider.CustomHostnamesConfig = CustomHostnamesConfig{Enabled: false}
|
||||
}
|
||||
hook.Reset()
|
||||
err := provider.ApplyChanges(context.Background(), tc.changes)
|
||||
assert.NoError(t, err, "ApplyChanges should not return error for newCloudFlareChange error (it should log and continue)")
|
||||
errorLogCount := 0
|
||||
for _, entry := range hook.Entries {
|
||||
if entry.Level == log.ErrorLevel &&
|
||||
strings.Contains(entry.Message, "failed to create cloudflare change") {
|
||||
errorLogCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, tc.errorLogCount, errorLogCount, "expected error log count for %s", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudFlareProvider_SupportedAdditionalRecordTypes(t *testing.T) {
|
||||
provider := &CloudFlareProvider{}
|
||||
|
||||
tests := []struct {
|
||||
recordType string
|
||||
expected bool
|
||||
}{
|
||||
{endpoint.RecordTypeMX, true},
|
||||
{endpoint.RecordTypeA, true},
|
||||
{endpoint.RecordTypeCNAME, true},
|
||||
{endpoint.RecordTypeTXT, true},
|
||||
{endpoint.RecordTypeNS, true},
|
||||
{"SRV", true},
|
||||
{"SPF", false},
|
||||
{"LOC", false},
|
||||
{"UNKNOWN", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.recordType, func(t *testing.T) {
|
||||
result := provider.SupportedAdditionalRecordTypes(tt.recordType)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
@ -302,20 +301,19 @@ func makeDomainEditRequest(domain, name, recordType, data string, ttl int) *godo
|
||||
}
|
||||
|
||||
if recordType == endpoint.RecordTypeMX {
|
||||
priority, domain, err := parseMxTarget(data)
|
||||
if err == nil {
|
||||
request.Priority = int(priority)
|
||||
request.Data = provider.EnsureTrailingDot(domain)
|
||||
} else {
|
||||
mxRecord, err := endpoint.NewMXRecord(data)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": name,
|
||||
"recordType": recordType,
|
||||
"data": data,
|
||||
}).Warn("Unable to parse MX target")
|
||||
return request
|
||||
}
|
||||
request.Priority = int(*mxRecord.GetPriority())
|
||||
request.Data = provider.EnsureTrailingDot(*mxRecord.GetHost())
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
@ -661,18 +659,3 @@ func (p *DigitalOceanProvider) ApplyChanges(ctx context.Context, planChanges *pl
|
||||
|
||||
return p.submitChanges(ctx, &changes)
|
||||
}
|
||||
|
||||
func parseMxTarget(mxTarget string) (priority int64, exchange string, err error) {
|
||||
targetParts := strings.SplitN(mxTarget, " ", 2)
|
||||
if len(targetParts) != 2 {
|
||||
return priority, exchange, fmt.Errorf("mx target needs to be of form '10 example.com'")
|
||||
}
|
||||
|
||||
priorityRaw, exchange := targetParts[0], targetParts[1]
|
||||
priority, err = strconv.ParseInt(priorityRaw, 10, 32)
|
||||
if err != nil {
|
||||
return priority, exchange, fmt.Errorf("invalid priority specified")
|
||||
}
|
||||
|
||||
return priority, exchange, nil
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
|
||||
}
|
||||
|
||||
func getSupportedTypes() []string {
|
||||
return []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS}
|
||||
return []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS, endpoint.RecordTypeMX}
|
||||
}
|
||||
|
||||
func (im *TXTRegistry) GetDomainFilter() endpoint.DomainFilterInterface {
|
||||
|
@ -119,6 +119,8 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
newEndpointWithOwner("txt.dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
|
||||
newEndpointWithOwner("txt.aaaa-dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("mail.test-zone.example.org", "10 onemail.example.com", endpoint.RecordTypeMX, ""),
|
||||
newEndpointWithOwner("txt.mx-mail.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -215,6 +217,14 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "owner-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "mail.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"10 onemail.example.com"},
|
||||
RecordType: endpoint.RecordTypeMX,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, false)
|
||||
@ -252,6 +262,8 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
newEndpointWithOwner("dualstack-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
|
||||
newEndpointWithOwner("aaaa-dualstack-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("mail.test-zone.example.org", "10 onemail.example.com", endpoint.RecordTypeMX, ""),
|
||||
newEndpointWithOwner("mx-mail-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -340,6 +352,14 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "owner-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "mail.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"10 onemail.example.com"},
|
||||
RecordType: endpoint.RecordTypeMX,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, false)
|
||||
@ -375,6 +395,8 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
|
||||
newEndpointWithOwner("aaaa-dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("mail.test-zone.example.org", "10 onemail.example.com", endpoint.RecordTypeMX, ""),
|
||||
newEndpointWithOwner("mx-mail.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -457,6 +479,14 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "owner-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "mail.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"10 onemail.example.com"},
|
||||
RecordType: endpoint.RecordTypeMX,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, false)
|
||||
@ -473,6 +503,8 @@ func testTXTRegistryRecordsPrefixedTemplated(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foo.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
|
||||
newEndpointWithOwner("txt-a.foo.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("mail.test-zone.example.org", "10 onemail.example.com", endpoint.RecordTypeMX, ""),
|
||||
newEndpointWithOwner("txt-mx.mail.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -484,6 +516,14 @@ func testTXTRegistryRecordsPrefixedTemplated(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "mail.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"10 onemail.example.com"},
|
||||
RecordType: endpoint.RecordTypeMX,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, false)
|
||||
@ -505,6 +545,8 @@ func testTXTRegistryRecordsSuffixedTemplated(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("bar.test-zone.example.org", "8.8.8.8", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("bartxtcname.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("mail.test-zone.example.org", "10 onemail.example.com", endpoint.RecordTypeMX, ""),
|
||||
newEndpointWithOwner("mailtxt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -516,6 +558,14 @@ func testTXTRegistryRecordsSuffixedTemplated(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "mail.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"10 onemail.example.com"},
|
||||
RecordType: endpoint.RecordTypeMX,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "txt%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, false)
|
||||
|
Loading…
Reference in New Issue
Block a user