• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / external-dns / 15535926526

09 Jun 2025 01:38PM UTC coverage: 76.245% (+0.008%) from 76.237%
15535926526

Pull #5510

github

mloiseleur
update changelog
Pull Request #5510: fix(chart): update schema with latest plugin release

14151 of 18560 relevant lines covered (76.24%)

759.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.49
/source/node.go
1
/*
2
Copyright 2017 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package source
18

19
import (
20
        "context"
21
        "fmt"
22
        "text/template"
23

24
        log "github.com/sirupsen/logrus"
25
        v1 "k8s.io/api/core/v1"
26
        "k8s.io/apimachinery/pkg/labels"
27
        kubeinformers "k8s.io/client-go/informers"
28
        coreinformers "k8s.io/client-go/informers/core/v1"
29
        "k8s.io/client-go/kubernetes"
30
        "k8s.io/client-go/tools/cache"
31

32
        "sigs.k8s.io/external-dns/endpoint"
33
        "sigs.k8s.io/external-dns/source/annotations"
34
        "sigs.k8s.io/external-dns/source/fqdn"
35
        "sigs.k8s.io/external-dns/source/informers"
36
)
37

38
const warningMsg = "The default behavior of exposing internal IPv6 addresses will change in the next minor version. Use --no-expose-internal-ipv6 flag to opt-in to the new behavior."
39

40
type nodeSource struct {
41
        client               kubernetes.Interface
42
        annotationFilter     string
43
        fqdnTemplate         *template.Template
44
        nodeInformer         coreinformers.NodeInformer
45
        labelSelector        labels.Selector
46
        excludeUnschedulable bool
47
        exposeInternalIPv6   bool
48
}
49

50
// NewNodeSource creates a new nodeSource with the given config.
51
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6, excludeUnschedulable bool) (Source, error) {
48✔
52
        tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
48✔
53
        if err != nil {
50✔
54
                return nil, err
2✔
55
        }
2✔
56

57
        // Use shared informers to listen for add/update/delete of nodes.
58
        // Set resync period to 0, to prevent processing when nothing has changed
59
        informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0)
46✔
60
        nodeInformer := informerFactory.Core().V1().Nodes()
46✔
61

46✔
62
        // Add default resource event handler to properly initialize informer.
46✔
63
        nodeInformer.Informer().AddEventHandler(
46✔
64
                cache.ResourceEventHandlerFuncs{
46✔
65
                        AddFunc: func(obj interface{}) {
89✔
66
                                log.Debug("node added")
43✔
67
                        },
43✔
68
                },
69
        )
70

71
        informerFactory.Start(ctx.Done())
46✔
72

46✔
73
        // wait for the local cache to be populated.
46✔
74
        if err := informers.WaitForCacheSync(context.Background(), informerFactory); err != nil {
46✔
75
                return nil, err
×
76
        }
×
77

78
        return &nodeSource{
46✔
79
                client:               kubeClient,
46✔
80
                annotationFilter:     annotationFilter,
46✔
81
                fqdnTemplate:         tmpl,
46✔
82
                nodeInformer:         nodeInformer,
46✔
83
                labelSelector:        labelSelector,
46✔
84
                excludeUnschedulable: excludeUnschedulable,
46✔
85
                exposeInternalIPv6:   exposeInternalIPv6,
46✔
86
        }, nil
46✔
87
}
88

89
// Endpoints returns endpoint objects for each service that should be processed.
90
func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
39✔
91
        nodes, err := ns.nodeInformer.Lister().List(ns.labelSelector)
39✔
92
        if err != nil {
39✔
93
                return nil, err
×
94
        }
×
95

96
        nodes, err = ns.filterByAnnotations(nodes)
39✔
97
        if err != nil {
39✔
98
                return nil, err
×
99
        }
×
100

101
        endpoints := map[endpoint.EndpointKey]*endpoint.Endpoint{}
39✔
102

39✔
103
        // create endpoints for all nodes
39✔
104
        for _, node := range nodes {
80✔
105
                // Check controller annotation to see if we are responsible.
41✔
106
                if controller, ok := node.Annotations[controllerAnnotationKey]; ok && controller != controllerAnnotationValue {
42✔
107
                        log.Debugf("Skipping node %s because controller value does not match, found: %s, required: %s",
1✔
108
                                node.Name, controller, controllerAnnotationValue)
1✔
109
                        continue
1✔
110
                }
111

112
                if node.Spec.Unschedulable && ns.excludeUnschedulable {
41✔
113
                        log.Debugf("Skipping node %s because it is unschedulable", node.Name)
1✔
114
                        continue
1✔
115
                }
116

117
                log.Debugf("creating endpoint for node %s", node.Name)
39✔
118

39✔
119
                ttl := annotations.TTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
39✔
120

39✔
121
                addrs := annotations.TargetsFromTargetAnnotation(node.Annotations)
39✔
122

39✔
123
                if len(addrs) == 0 {
76✔
124
                        addrs, err = ns.nodeAddresses(node)
37✔
125
                        if err != nil {
38✔
126
                                return nil, fmt.Errorf("failed to get node address from %s: %w", node.Name, err)
1✔
127
                        }
1✔
128
                }
129

130
                dnsNames := make(map[string]bool)
38✔
131

38✔
132
                if ns.fqdnTemplate != nil {
52✔
133
                        hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
14✔
134
                        if err != nil {
14✔
135
                                return nil, err
×
136
                        }
×
137

138
                        for _, name := range hostnames {
32✔
139
                                dnsNames[name] = true
18✔
140
                                log.Debugf("applied template for %s, converting to %s", node.Name, name)
18✔
141
                        }
18✔
142
                } else {
24✔
143
                        dnsNames[node.Name] = true
24✔
144
                        log.Debugf("not applying template for %s", node.Name)
24✔
145
                }
24✔
146

147
                for dns := range dnsNames {
80✔
148
                        log.Debugf("adding endpoint with %d targets", len(addrs))
42✔
149

42✔
150
                        for _, addr := range addrs {
101✔
151
                                ep := endpoint.NewEndpointWithTTL(dns, suitableType(addr), ttl)
59✔
152
                                log.Debugf("adding endpoint %s target %s", ep, addr)
59✔
153
                                key := endpoint.EndpointKey{
59✔
154
                                        DNSName:    ep.DNSName,
59✔
155
                                        RecordType: ep.RecordType,
59✔
156
                                }
59✔
157
                                if _, ok := endpoints[key]; !ok {
59✔
158
                                        epCopy := *ep
59✔
159
                                        epCopy.RecordType = key.RecordType
116✔
160
                                        endpoints[key] = &epCopy
57✔
161
                                }
57✔
162
                                endpoints[key].Targets = append(endpoints[key].Targets, addr)
57✔
163
                        }
57✔
164
                }
59✔
165
        }
166

167
        endpointsSlice := []*endpoint.Endpoint{}
168
        for _, ep := range endpoints {
169
                endpointsSlice = append(endpointsSlice, ep)
38✔
170
        }
95✔
171

57✔
172
        return endpointsSlice, nil
57✔
173
}
174

38✔
175
func (ns *nodeSource) AddEventHandler(_ context.Context, _ func()) {
176
}
177

×
178
// nodeAddress returns the node's externalIP and if that's not found, the node's internalIP
×
179
// basically what k8s.io/kubernetes/pkg/util/node.GetPreferredNodeAddress does
180
func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
181
        addresses := map[v1.NodeAddressType][]string{
182
                v1.NodeExternalIP: {},
37✔
183
                v1.NodeInternalIP: {},
37✔
184
        }
37✔
185
        var internalIpv6Addresses []string
37✔
186

37✔
187
        for _, addr := range node.Status.Addresses {
37✔
188
                // IPv6 InternalIP addresses have special handling.
37✔
189
                // Refer to https://github.com/kubernetes-sigs/external-dns/pull/5192 for more details.
92✔
190
                if addr.Type == v1.NodeInternalIP && suitableType(addr.Address) == endpoint.RecordTypeAAAA {
55✔
191
                        internalIpv6Addresses = append(internalIpv6Addresses, addr.Address)
55✔
192
                }
71✔
193
                addresses[addr.Type] = append(addresses[addr.Type], addr.Address)
16✔
194
        }
16✔
195

55✔
196
        if len(addresses[v1.NodeExternalIP]) > 0 {
197
                if ns.exposeInternalIPv6 {
198
                        log.Warn(warningMsg)
62✔
199
                        return append(addresses[v1.NodeExternalIP], internalIpv6Addresses...), nil
48✔
200
                }
23✔
201
                return addresses[v1.NodeExternalIP], nil
23✔
202
        }
23✔
203

2✔
204
        if len(addresses[v1.NodeInternalIP]) > 0 {
205
                return addresses[v1.NodeInternalIP], nil
206
        }
23✔
207

11✔
208
        return nil, fmt.Errorf("could not find node address for %s", node.Name)
11✔
209
}
210

1✔
211
// filterByAnnotations filters a list of nodes by a given annotation selector.
212
func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error) {
213
        selector, err := annotations.ParseFilter(ns.annotationFilter)
214
        if err != nil {
39✔
215
                return nil, err
39✔
216
        }
39✔
217

×
218
        // empty filter returns original list
×
219
        if selector.Empty() {
220
                return nodes, nil
221
        }
76✔
222

37✔
223
        var filteredList []*v1.Node
37✔
224

225
        for _, node := range nodes {
2✔
226
                // include a node if its annotations match the selector
2✔
227
                if selector.Matches(labels.Set(node.Annotations)) {
4✔
228
                        filteredList = append(filteredList, node)
2✔
229
                }
3✔
230
        }
1✔
231

1✔
232
        return filteredList, nil
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc