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

kubernetes-sigs / external-dns / 17648277439

11 Sep 2025 02:45PM UTC coverage: 78.36% (+0.9%) from 77.441%
17648277439

Pull #4823

github

troll-os
Make linter happy
Pull Request #4823: feat: add new flags to allow migration of OwnerID

19 of 19 new or added lines in 4 files covered. (100.0%)

121 existing lines in 6 files now uncovered.

15654 of 19977 relevant lines covered (78.36%)

733.13 hits per line

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

80.34
/source/f5_virtualserver.go
1
/*
2
Copyright 2022 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
        "errors"
22
        "fmt"
23
        "sort"
24
        "strings"
25

26
        log "github.com/sirupsen/logrus"
27
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28
        "k8s.io/apimachinery/pkg/labels"
29
        "k8s.io/apimachinery/pkg/runtime"
30
        "k8s.io/apimachinery/pkg/runtime/schema"
31
        "k8s.io/client-go/dynamic"
32
        "k8s.io/client-go/dynamic/dynamicinformer"
33
        kubeinformers "k8s.io/client-go/informers"
34
        "k8s.io/client-go/kubernetes"
35
        "k8s.io/client-go/kubernetes/scheme"
36
        "k8s.io/client-go/tools/cache"
37

38
        f5 "github.com/F5Networks/k8s-bigip-ctlr/v2/config/apis/cis/v1"
39

40
        "sigs.k8s.io/external-dns/endpoint"
41
        "sigs.k8s.io/external-dns/source/annotations"
42
        "sigs.k8s.io/external-dns/source/informers"
43
)
44

45
var f5VirtualServerGVR = schema.GroupVersionResource{
46
        Group:    "cis.f5.com",
47
        Version:  "v1",
48
        Resource: "virtualservers",
49
}
50

51
// virtualServerSource is an implementation of Source for F5 VirtualServer objects.
52
type f5VirtualServerSource struct {
53
        dynamicKubeClient     dynamic.Interface
54
        virtualServerInformer kubeinformers.GenericInformer
55
        kubeClient            kubernetes.Interface
56
        annotationFilter      string
57
        namespace             string
58
        unstructuredConverter *unstructuredConverter
59
}
60

61
func NewF5VirtualServerSource(
62
        ctx context.Context,
63
        dynamicKubeClient dynamic.Interface,
64
        kubeClient kubernetes.Interface,
65
        namespace string,
66
        annotationFilter string,
67
) (Source, error) {
15✔
68
        informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil)
15✔
69
        virtualServerInformer := informerFactory.ForResource(f5VirtualServerGVR)
15✔
70

15✔
71
        virtualServerInformer.Informer().AddEventHandler(
15✔
72
                cache.ResourceEventHandlerFuncs{
15✔
73
                        AddFunc: func(obj interface{}) {
29✔
74
                        },
14✔
75
                },
76
        )
77

78
        informerFactory.Start(ctx.Done())
15✔
79

15✔
80
        // wait for the local cache to be populated.
15✔
81
        if err := informers.WaitForDynamicCacheSync(context.Background(), informerFactory); err != nil {
15✔
82
                return nil, err
×
83
        }
×
84

85
        uc, err := newVSUnstructuredConverter()
15✔
86
        if err != nil {
15✔
87
                return nil, fmt.Errorf("failed to setup unstructured converter: %w", err)
×
88
        }
×
89

90
        return &f5VirtualServerSource{
15✔
91
                dynamicKubeClient:     dynamicKubeClient,
15✔
92
                virtualServerInformer: virtualServerInformer,
15✔
93
                kubeClient:            kubeClient,
15✔
94
                namespace:             namespace,
15✔
95
                annotationFilter:      annotationFilter,
15✔
96
                unstructuredConverter: uc,
15✔
97
        }, nil
15✔
98
}
99

100
// Endpoints returns endpoint objects for each host-target combination that should be processed.
101
// Retrieves all VirtualServers in the source's namespace(s).
102
func (vs *f5VirtualServerSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
14✔
103
        virtualServerObjects, err := vs.virtualServerInformer.Lister().ByNamespace(vs.namespace).List(labels.Everything())
14✔
104
        if err != nil {
14✔
105
                return nil, err
×
106
        }
×
107

108
        var virtualServers []*f5.VirtualServer
14✔
109
        for _, vsObj := range virtualServerObjects {
28✔
110
                unstructuredHost, ok := vsObj.(*unstructured.Unstructured)
14✔
111
                if !ok {
14✔
112
                        return nil, errors.New("could not convert")
×
113
                }
×
114

115
                virtualServer := &f5.VirtualServer{}
14✔
116
                err := vs.unstructuredConverter.scheme.Convert(unstructuredHost, virtualServer, nil)
14✔
117
                if err != nil {
14✔
118
                        return nil, err
×
119
                }
×
120
                virtualServers = append(virtualServers, virtualServer)
14✔
121
        }
122

123
        virtualServers, err = vs.filterByAnnotations(virtualServers)
14✔
124
        if err != nil {
14✔
125
                return nil, fmt.Errorf("failed to filter VirtualServers: %w", err)
×
126
        }
×
127

128
        endpoints, err := vs.endpointsFromVirtualServers(virtualServers)
14✔
129
        if err != nil {
14✔
130
                return nil, err
×
131
        }
×
132

133
        // Sort endpoints
134
        for _, ep := range endpoints {
33✔
135
                sort.Sort(ep.Targets)
19✔
136
        }
19✔
137

138
        return endpoints, nil
14✔
139
}
140

141
func (vs *f5VirtualServerSource) AddEventHandler(ctx context.Context, handler func()) {
×
142
        log.Debug("Adding event handler for VirtualServer")
×
143

×
144
        vs.virtualServerInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
×
145
}
×
146

147
// endpointsFromVirtualServers extracts the endpoints from a slice of VirtualServers
148
func (vs *f5VirtualServerSource) endpointsFromVirtualServers(virtualServers []*f5.VirtualServer) ([]*endpoint.Endpoint, error) {
14✔
149
        var endpoints []*endpoint.Endpoint
14✔
150

14✔
151
        for _, virtualServer := range virtualServers {
27✔
152
                if !hasValidVirtualServerIP(virtualServer) {
15✔
153
                        log.Warnf("F5 VirtualServer %s/%s is missing a valid IP address, skipping endpoint creation.",
2✔
154
                                virtualServer.Namespace, virtualServer.Name)
2✔
155
                        continue
2✔
156
                }
157

158
                resource := fmt.Sprintf("f5-virtualserver/%s/%s", virtualServer.Namespace, virtualServer.Name)
11✔
159

11✔
160
                ttl := annotations.TTLFromAnnotations(virtualServer.Annotations, resource)
11✔
161

11✔
162
                targets := annotations.TargetsFromTargetAnnotation(virtualServer.Annotations)
11✔
163
                if len(targets) == 0 && virtualServer.Spec.VirtualServerAddress != "" {
19✔
164
                        targets = append(targets, virtualServer.Spec.VirtualServerAddress)
8✔
165
                }
8✔
166

167
                if len(targets) == 0 && virtualServer.Status.VSAddress != "" {
12✔
168
                        targets = append(targets, virtualServer.Status.VSAddress)
1✔
169
                }
1✔
170

171
                endpoints = append(endpoints, EndpointsForHostname(virtualServer.Spec.Host, targets, ttl, nil, "", resource)...)
11✔
172
        }
11✔
173

20✔
174
        return endpoints, nil
17✔
175
}
8✔
176

8✔
177
// newUnstructuredConverter returns a new unstructuredConverter initialized
178
func newVSUnstructuredConverter() (*unstructuredConverter, error) {
179
        uc := &unstructuredConverter{
180
                scheme: runtime.NewScheme(),
14✔
181
        }
182

183
        // Add the core types we need
184
        uc.scheme.AddKnownTypes(f5VirtualServerGVR.GroupVersion(), &f5.VirtualServer{}, &f5.VirtualServerList{})
15✔
185
        if err := scheme.AddToScheme(uc.scheme); err != nil {
15✔
186
                return nil, err
15✔
187
        }
15✔
188

15✔
189
        return uc, nil
15✔
190
}
15✔
191

15✔
UNCOV
192
// filterByAnnotations filters a list of VirtualServers by a given annotation selector.
×
UNCOV
193
func (vs *f5VirtualServerSource) filterByAnnotations(virtualServers []*f5.VirtualServer) ([]*f5.VirtualServer, error) {
×
194
        selector, err := annotations.ParseFilter(vs.annotationFilter)
195
        if err != nil {
15✔
196
                return nil, err
197
        }
198

199
        // empty filter returns original list
14✔
200
        if selector.Empty() {
14✔
201
                return virtualServers, nil
14✔
UNCOV
202
        }
×
UNCOV
203

×
204
        filteredList := []*f5.VirtualServer{}
205

206
        for _, vs := range virtualServers {
26✔
207
                // include VirtualServer if its annotations match the selector
12✔
208
                if selector.Matches(labels.Set(vs.Annotations)) {
12✔
209
                        filteredList = append(filteredList, vs)
210
                }
2✔
211
        }
2✔
212

4✔
213
        return filteredList, nil
2✔
214
}
3✔
215

1✔
216
func hasValidVirtualServerIP(vs *f5.VirtualServer) bool {
1✔
217
        normalizedAddress := strings.ToLower(vs.Status.VSAddress)
218
        return normalizedAddress != "none" && normalizedAddress != ""
219
}
2✔
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