Skip to content

Commit 9f427e5

Browse files
chore(source): code cleanup
1 parent 7ae0405 commit 9f427e5

36 files changed

+1772
-842
lines changed

provider/aws/aws.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ func (z zoneTags) filterZonesByTags(p *AWSProvider, zones map[string]*profiledZo
254254
// append adds tags to the ZoneTags for a given zoneID.
255255
func (z zoneTags) append(id string, tags []route53types.Tag) {
256256
zoneId := fmt.Sprintf("/hostedzone/%s", id)
257-
if _, exists := z[zoneId]; !exists {
257+
if _, ok := z[zoneId]; !ok {
258258
z[zoneId] = make(map[string]string)
259259
}
260260
for _, tag := range tags {

provider/cloudflare/cloudflare.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434
"sigs.k8s.io/external-dns/endpoint"
3535
"sigs.k8s.io/external-dns/plan"
3636
"sigs.k8s.io/external-dns/provider"
37-
"sigs.k8s.io/external-dns/source"
37+
"sigs.k8s.io/external-dns/source/annotations"
3838
)
3939

4040
const (
@@ -736,17 +736,17 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]
736736
if proxied {
737737
e.RecordTTL = 0
738738
}
739-
e.SetProviderSpecificProperty(source.CloudflareProxiedKey, strconv.FormatBool(proxied))
739+
e.SetProviderSpecificProperty(annotations.CloudflareProxiedKey, strconv.FormatBool(proxied))
740740

741741
if p.CustomHostnamesConfig.Enabled {
742742
// sort custom hostnames in annotation to properly detect changes
743743
if customHostnames := getEndpointCustomHostnames(e); len(customHostnames) > 1 {
744744
sort.Strings(customHostnames)
745-
e.SetProviderSpecificProperty(source.CloudflareCustomHostnameKey, strings.Join(customHostnames, ","))
745+
e.SetProviderSpecificProperty(annotations.CloudflareCustomHostnameKey, strings.Join(customHostnames, ","))
746746
}
747747
} else {
748748
// ignore custom hostnames annotations if not enabled
749-
e.DeleteProviderSpecificProperty(source.CloudflareCustomHostnameKey)
749+
e.DeleteProviderSpecificProperty(annotations.CloudflareCustomHostnameKey)
750750
}
751751

752752
adjustedEndpoints = append(adjustedEndpoints, e)
@@ -928,10 +928,10 @@ func shouldBeProxied(ep *endpoint.Endpoint, proxiedByDefault bool) bool {
928928
proxied := proxiedByDefault
929929

930930
for _, v := range ep.ProviderSpecific {
931-
if v.Name == source.CloudflareProxiedKey {
931+
if v.Name == annotations.CloudflareProxiedKey {
932932
b, err := strconv.ParseBool(v.Value)
933933
if err != nil {
934-
log.Errorf("Failed to parse annotation [%q]: %v", source.CloudflareProxiedKey, err)
934+
log.Errorf("Failed to parse annotation [%q]: %v", annotations.CloudflareProxiedKey, err)
935935
} else {
936936
proxied = b
937937
}
@@ -951,7 +951,7 @@ func getRegionKey(endpoint *endpoint.Endpoint, defaultRegionKey string) string {
951951
}
952952

953953
for _, v := range endpoint.ProviderSpecific {
954-
if v.Name == source.CloudflareRegionKey {
954+
if v.Name == annotations.CloudflareRegionKey {
955955
return v.Value
956956
}
957957
}
@@ -960,7 +960,7 @@ func getRegionKey(endpoint *endpoint.Endpoint, defaultRegionKey string) string {
960960

961961
func getEndpointCustomHostnames(ep *endpoint.Endpoint) []string {
962962
for _, v := range ep.ProviderSpecific {
963-
if v.Name == source.CloudflareCustomHostnameKey {
963+
if v.Name == annotations.CloudflareCustomHostnameKey {
964964
customHostnames := strings.Split(v.Value, ",")
965965
return customHostnames
966966
}
@@ -1015,11 +1015,11 @@ func groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHost
10151015
if e == nil {
10161016
continue
10171017
}
1018-
e = e.WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(proxied))
1018+
e = e.WithProviderSpecific(annotations.CloudflareProxiedKey, strconv.FormatBool(proxied))
10191019
// noop (customHostnames is empty) if custom hostnames feature is not in use
10201020
if customHostnames, ok := customHostnames[records[0].Name]; ok {
10211021
sort.Strings(customHostnames)
1022-
e = e.WithProviderSpecific(source.CloudflareCustomHostnameKey, strings.Join(customHostnames, ","))
1022+
e = e.WithProviderSpecific(annotations.CloudflareCustomHostnameKey, strings.Join(customHostnames, ","))
10231023
}
10241024

10251025
endpoints = append(endpoints, e)

provider/inmemory/inmemory.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,28 +312,28 @@ func (c *inMemoryClient) validateChangeBatch(zone string, changes *plan.Changes)
312312
}
313313
mesh := sets.New[endpoint.EndpointKey]()
314314
for _, newEndpoint := range changes.Create {
315-
if _, exists := curZone[newEndpoint.Key()]; exists {
315+
if _, ok := curZone[newEndpoint.Key()]; ok {
316316
return ErrRecordAlreadyExists
317317
}
318318
if err := c.updateMesh(mesh, newEndpoint); err != nil {
319319
return err
320320
}
321321
}
322322
for _, updateEndpoint := range changes.UpdateNew {
323-
if _, exists := curZone[updateEndpoint.Key()]; !exists {
323+
if _, ok := curZone[updateEndpoint.Key()]; !ok {
324324
return ErrRecordNotFound
325325
}
326326
if err := c.updateMesh(mesh, updateEndpoint); err != nil {
327327
return err
328328
}
329329
}
330330
for _, updateOldEndpoint := range changes.UpdateOld {
331-
if rec, exists := curZone[updateOldEndpoint.Key()]; !exists || rec.Targets[0] != updateOldEndpoint.Targets[0] {
331+
if rec, ok := curZone[updateOldEndpoint.Key()]; !ok || rec.Targets[0] != updateOldEndpoint.Targets[0] {
332332
return ErrRecordNotFound
333333
}
334334
}
335335
for _, deleteEndpoint := range changes.Delete {
336-
if rec, exists := curZone[deleteEndpoint.Key()]; !exists || rec.Targets[0] != deleteEndpoint.Targets[0] {
336+
if rec, ok := curZone[deleteEndpoint.Key()]; !ok || rec.Targets[0] != deleteEndpoint.Targets[0] {
337337
return ErrRecordNotFound
338338
}
339339
if err := c.updateMesh(mesh, deleteEndpoint); err != nil {

registry/dynamodb_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ func (r *DynamoDBStub) BatchExecuteStatement(context context.Context, input *dyn
12481248

12491249
var key string
12501250
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[0], &key))
1251-
if code, exists := r.stubConfig.ExpectInsertError[key]; exists {
1251+
if code, ok := r.stubConfig.ExpectInsertError[key]; ok {
12521252
delete(r.stubConfig.ExpectInsertError, key)
12531253
responses = append(responses, dynamodbtypes.BatchStatementResponse{
12541254
Error: &dynamodbtypes.BatchStatementError{

source/ambassador_host.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"k8s.io/client-go/tools/cache"
3939

4040
"sigs.k8s.io/external-dns/endpoint"
41+
"sigs.k8s.io/external-dns/source/annotations"
4142
)
4243

4344
// ambHostAnnotation is the annotation in the Host that maps to a Service
@@ -119,7 +120,7 @@ func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endp
119120
}
120121

121122
// Get a list of Ambassador Host resources
122-
ambassadorHosts := []*ambassador.Host{}
123+
var ambassadorHosts []*ambassador.Host
123124
for _, hostObj := range hosts {
124125
unstructuredHost, ok := hostObj.(*unstructured.Unstructured)
125126
if !ok {
@@ -140,7 +141,7 @@ func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endp
140141
return nil, errors.Wrap(err, "failed to filter Ambassador Hosts by annotation")
141142
}
142143

143-
endpoints := []*endpoint.Endpoint{}
144+
var endpoints []*endpoint.Endpoint
144145

145146
for _, host := range ambassadorHosts {
146147
fullname := fmt.Sprintf("%s/%s", host.Namespace, host.Name)
@@ -152,7 +153,7 @@ func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endp
152153
continue
153154
}
154155

155-
targets := getTargetsFromTargetAnnotation(host.Annotations)
156+
targets := annotations.TargetsFromTargetAnnotation(host.Annotations)
156157
if len(targets) == 0 {
157158
targets, err = sc.targetsFromAmbassadorLoadBalancer(ctx, service)
158159
if err != nil {
@@ -185,11 +186,10 @@ func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endp
185186
// endpointsFromHost extracts the endpoints from a Host object
186187
func (sc *ambassadorHostSource) endpointsFromHost(host *ambassador.Host, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
187188
var endpoints []*endpoint.Endpoint
188-
annotations := host.Annotations
189189

190190
resource := fmt.Sprintf("host/%s/%s", host.Namespace, host.Name)
191-
providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations)
192-
ttl := getTTLFromAnnotations(annotations, resource)
191+
providerSpecific, setIdentifier := annotations.ProviderSpecificAnnotations(host.Annotations)
192+
ttl := annotations.TTLFromAnnotations(host.Annotations, resource)
193193

194194
if host.Spec != nil {
195195
hostname := host.Spec.Hostname

source/ambassador_host_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
fakeDynamic "k8s.io/client-go/dynamic/fake"
3434
fakeKube "k8s.io/client-go/kubernetes/fake"
3535
"sigs.k8s.io/external-dns/endpoint"
36+
"sigs.k8s.io/external-dns/source/annotations"
3637
)
3738

3839
const defaultAmbassadorNamespace = "ambassador"
@@ -246,8 +247,8 @@ func TestAmbassadorHostSource(t *testing.T) {
246247
ObjectMeta: metav1.ObjectMeta{
247248
Name: "basic-host",
248249
Annotations: map[string]string{
249-
ambHostAnnotation: hostAnnotation,
250-
CloudflareProxiedKey: "true",
250+
ambHostAnnotation: hostAnnotation,
251+
annotations.CloudflareProxiedKey: "true",
251252
},
252253
},
253254
Spec: &ambassador.HostSpec{

source/annotations/annotations.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package annotations
15+
16+
import (
17+
"math"
18+
)
19+
20+
const (
21+
// CloudflareProxiedKey The annotation used for determining if traffic will go through Cloudflare
22+
CloudflareProxiedKey = "external-dns.alpha.kubernetes.io/cloudflare-proxied"
23+
CloudflareCustomHostnameKey = "external-dns.alpha.kubernetes.io/cloudflare-custom-hostname"
24+
CloudflareRegionKey = "external-dns.alpha.kubernetes.io/cloudflare-region-key"
25+
26+
AWSPrefix = "external-dns.alpha.kubernetes.io/aws-"
27+
SCWPrefix = "external-dns.alpha.kubernetes.io/scw-"
28+
IBMCloudPrefix = "external-dns.alpha.kubernetes.io/ibmcloud-"
29+
WebhookPrefix = "external-dns.alpha.kubernetes.io/webhook-"
30+
CloudflarePrefix = "external-dns.alpha.kubernetes.io/cloudflare-"
31+
32+
TtlKey = "external-dns.alpha.kubernetes.io/ttl"
33+
ttlMinimum = 1
34+
ttlMaximum = math.MaxInt32
35+
36+
SetIdentifierKey = "external-dns.alpha.kubernetes.io/set-identifier"
37+
AliasKey = "external-dns.alpha.kubernetes.io/alias"
38+
TargetKey = "external-dns.alpha.kubernetes.io/target"
39+
// The annotation used for figuring out which controller is responsible
40+
ControllerKey = "external-dns.alpha.kubernetes.io/controller"
41+
// The annotation used for defining the desired hostname
42+
HostnameKey = "external-dns.alpha.kubernetes.io/hostname"
43+
// The annotation used for specifying whether the public or private interface address is used
44+
AccessKey = "external-dns.alpha.kubernetes.io/access"
45+
// The annotation used for specifying the type of endpoints to use for headless services
46+
EndpointsTypeKey = "external-dns.alpha.kubernetes.io/endpoints-type"
47+
// The annotation used to determine the source of hostnames for ingresses. This is an optional field - all
48+
// available hostname sources are used if not specified.
49+
IngressHostnameSourceKey = "external-dns.alpha.kubernetes.io/ingress-hostname-source"
50+
// The value of the controller annotation so that we feel responsible
51+
ControllerValue = "dns-controller"
52+
// The annotation used for defining the desired hostname
53+
InternalHostnameKey = "external-dns.alpha.kubernetes.io/internal-hostname"
54+
)

source/annotations/processors.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package annotations
15+
16+
import (
17+
"strconv"
18+
"strings"
19+
"time"
20+
21+
log "github.com/sirupsen/logrus"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/labels"
24+
25+
"sigs.k8s.io/external-dns/endpoint"
26+
)
27+
28+
func hasAliasFromAnnotations(annotations map[string]string) bool {
29+
aliasAnnotation, ok := annotations[AliasKey]
30+
return ok && aliasAnnotation == "true"
31+
}
32+
33+
// TTLFromAnnotations extracts the TTL from the annotations of the given resource.
34+
func TTLFromAnnotations(annotations map[string]string, resource string) endpoint.TTL {
35+
ttlNotConfigured := endpoint.TTL(0)
36+
ttlAnnotation, ok := annotations[TtlKey]
37+
if !ok {
38+
return ttlNotConfigured
39+
}
40+
ttlValue, err := parseTTL(ttlAnnotation)
41+
if err != nil {
42+
log.Warnf("%s: %q is not a valid TTL value: %v", resource, ttlAnnotation, err)
43+
return ttlNotConfigured
44+
}
45+
if ttlValue < ttlMinimum || ttlValue > ttlMaximum {
46+
log.Warnf("TTL value %q must be between [%d, %d]", ttlValue, ttlMinimum, ttlMaximum)
47+
return ttlNotConfigured
48+
}
49+
return endpoint.TTL(ttlValue)
50+
}
51+
52+
// parseTTL parses TTL from string, returning duration in seconds.
53+
// parseTTL supports both integers like "600" and durations based
54+
// on Go Duration like "10m", hence "600" and "10m" represent the same value.
55+
//
56+
// Note: for durations like "1.5s" the fraction is omitted (resulting in 1 second for the example).
57+
func parseTTL(s string) (int64, error) {
58+
ttlDuration, errDuration := time.ParseDuration(s)
59+
if errDuration != nil {
60+
ttlInt, err := strconv.ParseInt(s, 10, 64)
61+
if err != nil {
62+
return 0, errDuration
63+
}
64+
return ttlInt, nil
65+
}
66+
67+
return int64(ttlDuration.Seconds()), nil
68+
}
69+
70+
// ParseFilter parses an annotation filter string into a labels.Selector.
71+
// Returns nil if the annotation filter is invalid.
72+
func ParseFilter(annotationFilter string) (labels.Selector, error) {
73+
labelSelector, err := metav1.ParseToLabelSelector(annotationFilter)
74+
if err != nil {
75+
return nil, err
76+
}
77+
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
78+
if err != nil {
79+
return nil, err
80+
}
81+
return selector, nil
82+
}
83+
84+
// TargetsFromTargetAnnotation gets endpoints from optional "target" annotation.
85+
// Returns empty endpoints array if none are found.
86+
func TargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets {
87+
var targets endpoint.Targets
88+
// Get the desired hostname of the ingress from the annotation.
89+
targetAnnotation, ok := annotations[TargetKey]
90+
if ok && targetAnnotation != "" {
91+
// splits the hostname annotation and removes the trailing periods
92+
targetsList := SplitHostnameAnnotation(targetAnnotation)
93+
for _, targetHostname := range targetsList {
94+
targetHostname = strings.TrimSuffix(targetHostname, ".")
95+
targets = append(targets, targetHostname)
96+
}
97+
}
98+
return targets
99+
}
100+
101+
// HostnamesFromAnnotations extracts the hostnames from the given annotations map.
102+
// It returns a slice of hostnames if the HostnameKey annotation is present, otherwise it returns nil.
103+
func HostnamesFromAnnotations(input map[string]string) []string {
104+
return extractHostnamesFromAnnotations(input, HostnameKey)
105+
}
106+
107+
// InternalHostnamesFromAnnotations extracts the internal hostnames from the given annotations map.
108+
// It returns a slice of internal hostnames if the InternalHostnameKey annotation is present, otherwise it returns nil.
109+
func InternalHostnamesFromAnnotations(input map[string]string) []string {
110+
return extractHostnamesFromAnnotations(input, InternalHostnameKey)
111+
}
112+
113+
// SplitHostnameAnnotation splits a comma-separated hostname annotation string into a slice of hostnames.
114+
// It trims any leading or trailing whitespace and removes any spaces within the anno
115+
func SplitHostnameAnnotation(input string) []string {
116+
return strings.Split(strings.TrimSpace(strings.ReplaceAll(input, " ", "")), ",")
117+
}
118+
119+
func extractHostnamesFromAnnotations(input map[string]string, key string) []string {
120+
annotation, ok := input[key]
121+
if !ok {
122+
return nil
123+
}
124+
return SplitHostnameAnnotation(annotation)
125+
}

0 commit comments

Comments
 (0)