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

kubernetes-sigs / external-dns / 15063173426

16 May 2025 07:32AM UTC coverage: 73.121% (+0.8%) from 72.283%
15063173426

Pull #5353

github

ivankatliarchuk
chore(docs): update aws permissions

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5353: chore(docs): update aws role requirements with conditions

14859 of 20321 relevant lines covered (73.12%)

694.09 hits per line

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

78.81
/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
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29
        "k8s.io/apimachinery/pkg/labels"
30
        "k8s.io/apimachinery/pkg/runtime"
31
        "k8s.io/apimachinery/pkg/runtime/schema"
32
        "k8s.io/client-go/dynamic"
33
        "k8s.io/client-go/dynamic/dynamicinformer"
34
        "k8s.io/client-go/informers"
35
        "k8s.io/client-go/kubernetes"
36
        "k8s.io/client-go/kubernetes/scheme"
37
        "k8s.io/client-go/tools/cache"
38

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

41
        "sigs.k8s.io/external-dns/endpoint"
42
)
43

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

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

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

10✔
70
        virtualServerInformer.Informer().AddEventHandler(
10✔
71
                cache.ResourceEventHandlerFuncs{
10✔
72
                        AddFunc: func(obj interface{}) {
10✔
73
                        },
19✔
74
                },
9✔
75
        )
76

77
        informerFactory.Start(ctx.Done())
78

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

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

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

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

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

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

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

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

×
132
        // Sort endpoints
133
        for _, ep := range endpoints {
134
                sort.Sort(ep.Targets)
14✔
135
        }
5✔
136

5✔
137
        return endpoints, nil
138
}
9✔
139

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

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

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

9✔
150
        for _, virtualServer := range virtualServers {
9✔
151
                if !isVirtualServerReady(virtualServer) {
17✔
152
                        log.Warnf("F5 VirtualServer %s/%s is not ready or is missing an IP address, skipping endpoint creation.",
11✔
153
                                virtualServer.Namespace, virtualServer.Name)
3✔
154
                        continue
3✔
155
                }
3✔
156

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

5✔
159
                ttl := getTTLFromAnnotations(virtualServer.Annotations, resource)
5✔
160

5✔
161
                targets := getTargetsFromTargetAnnotation(virtualServer.Annotations)
5✔
162
                if len(targets) == 0 && virtualServer.Spec.VirtualServerAddress != "" {
5✔
163
                        targets = append(targets, virtualServer.Spec.VirtualServerAddress)
8✔
164
                }
3✔
165

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

1✔
170
                endpoints = append(endpoints, endpointsForHostname(virtualServer.Spec.Host, targets, ttl, nil, "", resource)...)
171
        }
5✔
172

173
        return endpoints, nil
174
}
9✔
175

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

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

×
188
        return uc, nil
189
}
10✔
190

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

×
198
        selector, err := metav1.LabelSelectorAsSelector(labelSelector)
199
        if err != nil {
9✔
200
                return nil, err
9✔
201
        }
×
202

×
203
        // empty filter returns original list
204
        if selector.Empty() {
205
                return virtualServers, nil
16✔
206
        }
7✔
207

7✔
208
        filteredList := []*f5.VirtualServer{}
209

2✔
210
        for _, vs := range virtualServers {
2✔
211
                // convert the VirtualServer's annotations to an equivalent label selector
4✔
212
                annotations := labels.Set(vs.Annotations)
2✔
213

3✔
214
                // include VirtualServer if its annotations match the selector
1✔
215
                if selector.Matches(annotations) {
1✔
216
                        filteredList = append(filteredList, vs)
217
                }
218
        }
2✔
219

220
        return filteredList, nil
221
}
8✔
222

10✔
223
func isVirtualServerReady(vs *f5.VirtualServer) bool {
2✔
224
        if strings.ToLower(vs.Status.Status) != "ok" {
2✔
225
                return false
226
        }
6✔
227

6✔
228
        normalizedAddress := strings.ToLower(vs.Status.VSAddress)
229
        return normalizedAddress != "none" && normalizedAddress != ""
230
}
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