Skip to content

feat: introduce a Tags field to the Endpoint struct to support DNS record tags #5478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ If using a txt registry and attempting to use a CNAME the `--txt-prefix` must be
If `externalIPs` list is defined for a `LoadBalancer` service, this list will be used instead of an assigned load balancer IP to create a DNS record.
It's useful when you run bare metal Kubernetes clusters behind NAT or in a similar setup, where a load balancer IP differs from a public IP (e.g. with [MetalLB](https://metallb.universe.tf)).

## DNS Record Tags Support

ExternalDNS now supports DNS record tags via the `Tags` field in the Endpoint struct. This feature is currently supported by some providers, such as Cloudflare (may require a paid plan).
Tags allow you to add metadata to DNS records for advanced management and filtering. See provider documentation for details and limitations.

## Contributing

Are you interested in contributing to external-dns? We, the maintainers and community, would love your
Expand Down
3 changes: 3 additions & 0 deletions docs/sources/crd.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Endpoint struct {
// ProviderSpecific stores provider specific config
// +optional
ProviderSpecific ProviderSpecific `json:"providerSpecific,omitempty"`
// Tags stores DNS record tags (supported by some providers, e.g., Cloudflare)
// +optional
Tags []string `json:"tags,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure here, what the right design should be. Tags are prodiver specific, so most likely it should be part of ProviderSpecific, unfortunately ProviderSpecific is Name,Value. We may need a better ProviderSpecific abstraction. But this will be a refactoring of current code, more effort then current solution.

This is just my 50 cents

@mloiseleur @7onn wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As well as at the moment not sure how many providers do support tags, it's a great feature by itself, but gcp or aws do not support tagging individual DNS resource records

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Rework correctly the abstraction would be simpler when we have multiple providers supporting tags. So, how about keep existing CRD and use it like that:

spec:
  endpoints:
    - dnsName: foo.com
      providerSpecific:
         name: "tags"
         value: "[\"taga:valuea\", \"tagb:valueb\"]"

ie: convert the providerSpecific value named tags as the expected array ?

And when we have multiple providers with tags, we can introduce a dedicated field to it, with better schema and CEL validation ?

Copy link
Contributor

@7onn 7onn May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure here, what the right design should be. Tags are prodiver specific, so most likely it should be part of ProviderSpecific

Making this provider specific helps with decoupling the code but also foments redundancy. So unless the majority of the providers supports it, I'd argue for keeping it provider specific for the sake of implementation simplicity (even though increasing the amount of code).

Unfortunately, I had opened this before checking this notification and realizing tagging was to be handled in this PR, so we might have worked redundantly. Looking forward to coordinate how to proceed with this :)

}

type DNSEndpointSpec struct {
Expand Down
5 changes: 5 additions & 0 deletions endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ type Endpoint struct {
// ProviderSpecific stores provider specific config
// +optional
ProviderSpecific ProviderSpecific `json:"providerSpecific,omitempty"`
// Tags stores DNS record tags (for providers that support them, e.g. Cloudflare)
// +optional
// Tags are only supported by some providers and may require a paid plan (e.g. Cloudflare).
Tags []string `json:"tags,omitempty"`
}

// NewEndpoint initialization method to be used to create an endpoint
Expand Down Expand Up @@ -247,6 +251,7 @@ func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string)
RecordType: recordType,
Labels: NewLabels(),
RecordTTL: ttl,
Tags: nil, // default to nil, can be set later
}
}

Expand Down
23 changes: 23 additions & 0 deletions endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,26 @@ func TestPDNScheckEndpoint(t *testing.T) {
assert.Equal(t, tt.expected, actual)
}
}

func TestEndpointTags(t *testing.T) {
t.Run("Tags default to nil", func(t *testing.T) {
e := NewEndpoint("example.org", "A", "1.2.3.4")
assert.Nil(t, e.Tags)
})

t.Run("Tags can be set and retrieved", func(t *testing.T) {
e := NewEndpoint("example.org", "A", "1.2.3.4")
tags := []string{"foo", "bar"}
e.Tags = tags
assert.Equal(t, tags, e.Tags)
})

t.Run("Tags equality", func(t *testing.T) {
e1 := NewEndpoint("example.org", "A", "1.2.3.4")
e2 := NewEndpoint("example.org", "A", "1.2.3.4")
tags := []string{"tag1", "tag2"}
e1.Tags = tags
e2.Tags = tags
assert.Equal(t, e1.Tags, e2.Tags)
})
}