diff --git a/main.go b/main.go index 50f4e0451..6bcffcf5a 100644 --- a/main.go +++ b/main.go @@ -299,7 +299,7 @@ func main() { case "noop": r, err = registry.NewNoopRegistry(p) case "txt": - r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval) + r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement) case "aws-sd": r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID) default: diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 7d718d670..908fae8a7 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -119,6 +119,7 @@ type Config struct { MetricsAddress string LogLevel string TXTCacheInterval time.Duration + TXTWildcardReplacement string ExoscaleEndpoint string ExoscaleAPIKey string `secure:"yes"` ExoscaleAPISecret string `secure:"yes"` @@ -210,6 +211,7 @@ var defaultConfig = &Config{ TXTPrefix: "", TXTSuffix: "", TXTCacheInterval: 0, + TXTWildcardReplacement: "", Interval: time.Minute, Once: false, DryRun: false, @@ -401,6 +403,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("txt-owner-id", "When using the TXT registry, a name that identifies this instance of ExternalDNS (default: default)").Default(defaultConfig.TXTOwnerID).StringVar(&cfg.TXTOwnerID) app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Mutual exclusive with txt-suffix!").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix) app.Flag("txt-suffix", "When using the TXT registry, a custom string that's suffixed to the host portion of each ownership DNS record (optional). Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix) + app.Flag("txt-wildcard-replacement", "When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional)").Default(defaultConfig.TXTWildcardReplacement).StringVar(&cfg.TXTWildcardReplacement) // Flags related to the main control loop app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval) diff --git a/registry/txt.go b/registry/txt.go index b4cb97a79..7bae2dfaa 100644 --- a/registry/txt.go +++ b/registry/txt.go @@ -40,10 +40,13 @@ type TXTRegistry struct { recordsCache []*endpoint.Endpoint recordsCacheRefreshTime time.Time cacheInterval time.Duration + + // optional string to use to replace the asterisk in wildcard entries + wildcardReplacement string } // NewTXTRegistry returns new TXTRegistry object -func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration) (*TXTRegistry, error) { +func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string) (*TXTRegistry, error) { if ownerID == "" { return nil, errors.New("owner id cannot be empty") } @@ -52,13 +55,14 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st return nil, errors.New("txt-prefix and txt-suffix are mutual exclusive") } - mapper := newaffixNameMapper(txtPrefix, txtSuffix) + mapper := newaffixNameMapper(txtPrefix, txtSuffix, txtWildcardReplacement) return &TXTRegistry{ - provider: provider, - ownerID: ownerID, - mapper: mapper, - cacheInterval: cacheInterval, + provider: provider, + ownerID: ownerID, + mapper: mapper, + cacheInterval: cacheInterval, + wildcardReplacement: txtWildcardReplacement, }, nil } @@ -107,7 +111,13 @@ func (im *TXTRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error if ep.Labels == nil { ep.Labels = endpoint.NewLabels() } - key := fmt.Sprintf("%s::%s", ep.DNSName, ep.SetIdentifier) + dnsNameSplit := strings.Split(ep.DNSName, ".") + // If specified, replace a leading asterisk in the generated txt record name with some other string + if im.wildcardReplacement != "" && dnsNameSplit[0] == "*" { + dnsNameSplit[0] = im.wildcardReplacement + } + dnsName := strings.Join(dnsNameSplit, ".") + key := fmt.Sprintf("%s::%s", dnsName, ep.SetIdentifier) if labels, ok := labelMap[key]; ok { for k, v := range labels { ep.Labels[k] = v @@ -211,14 +221,15 @@ type nameMapper interface { } type affixNameMapper struct { - prefix string - suffix string + prefix string + suffix string + wildcardReplacement string } var _ nameMapper = affixNameMapper{} -func newaffixNameMapper(prefix string, suffix string) affixNameMapper { - return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix)} +func newaffixNameMapper(prefix string, suffix string, wildcardReplacement string) affixNameMapper { + return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix), wildcardReplacement: wildcardReplacement} } func (pr affixNameMapper) toEndpointName(txtDNSName string) string { @@ -238,6 +249,10 @@ func (pr affixNameMapper) toEndpointName(txtDNSName string) string { func (pr affixNameMapper) toTXTName(endpointDNSName string) string { DNSName := strings.SplitN(endpointDNSName, ".", 2) + // If specified, replace a leading asterisk in the generated txt record name with some other string + if pr.wildcardReplacement != "" && DNSName[0] == "*" { + DNSName[0] = pr.wildcardReplacement + } return pr.prefix + DNSName[0] + pr.suffix + "." + DNSName[1] } diff --git a/registry/txt_test.go b/registry/txt_test.go index 5157dd560..d23e728c0 100644 --- a/registry/txt_test.go +++ b/registry/txt_test.go @@ -44,20 +44,20 @@ func TestTXTRegistry(t *testing.T) { func testTXTRegistryNew(t *testing.T) { p := inmemory.NewInMemoryProvider() - _, err := NewTXTRegistry(p, "txt", "", "", time.Hour) + _, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "") require.Error(t, err) - _, err = NewTXTRegistry(p, "", "txt", "", time.Hour) + _, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "") require.Error(t, err) - r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour) + r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "") require.NoError(t, err) assert.Equal(t, p, r.provider) - r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour) + r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "") require.NoError(t, err) - _, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour) + _, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "") require.Error(t, err) _, ok := r.mapper.(affixNameMapper) @@ -65,7 +65,7 @@ func testTXTRegistryNew(t *testing.T) { assert.Equal(t, "owner", r.ownerID) assert.Equal(t, p, r.provider) - r, err = NewTXTRegistry(p, "", "", "owner", time.Hour) + r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "") require.NoError(t, err) _, ok = r.mapper.(affixNameMapper) @@ -97,6 +97,8 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) { newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"), newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"), newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"), + newEndpointWithOwner("*.wildcard.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""), + newEndpointWithOwner("txt.wc.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""), }, }) expectedRecords := []*endpoint.Endpoint{ @@ -169,15 +171,23 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) { endpoint.OwnerLabelKey: "", }, }, + { + DNSName: "*.wildcard.test-zone.example.org", + Targets: endpoint.Targets{"foo.loadbalancer.com"}, + RecordType: endpoint.RecordTypeCNAME, + Labels: map[string]string{ + endpoint.OwnerLabelKey: "owner", + }, + }, } - r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc") records, _ := r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) // Ensure prefix is case-insensitive - r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour) + r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpointLabels(records, expectedRecords)) @@ -276,13 +286,13 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "") records, _ := r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) // Ensure prefix is case-insensitive - r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour) + r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpointLabels(records, expectedRecords)) @@ -357,7 +367,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "") records, _ := r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -394,7 +404,7 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) { newEndpointWithOwner("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"), }, }) - r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -485,12 +495,13 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) { newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"), }, }) - r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"), newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", "", "", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"), + newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"), }, Delete: []*endpoint.Endpoint{ newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"), @@ -511,6 +522,8 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) { newEndpointWithOwner("new-record-1-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""), newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", "", "owner", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"), newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-3"), + newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "owner", "ingress/default/my-ingress"), + newEndpointWithOwner("wildcard-txt.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""), }, Delete: []*endpoint.Endpoint{ newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"), @@ -572,7 +585,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) { newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""), }, }) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour) + r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{