sigs.k8s.io/external-dns@v0.14.1/source/f5_virtualserver.go (about) 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 "fmt" 22 "sort" 23 24 "github.com/pkg/errors" 25 log "github.com/sirupsen/logrus" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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 "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 ) 42 43 var f5VirtualServerGVR = schema.GroupVersionResource{ 44 Group: "cis.f5.com", 45 Version: "v1", 46 Resource: "virtualservers", 47 } 48 49 // virtualServerSource is an implementation of Source for F5 VirtualServer objects. 50 type f5VirtualServerSource struct { 51 dynamicKubeClient dynamic.Interface 52 virtualServerInformer informers.GenericInformer 53 kubeClient kubernetes.Interface 54 annotationFilter string 55 namespace string 56 unstructuredConverter *unstructuredConverter 57 } 58 59 func NewF5VirtualServerSource( 60 ctx context.Context, 61 dynamicKubeClient dynamic.Interface, 62 kubeClient kubernetes.Interface, 63 namespace string, 64 annotationFilter string, 65 ) (Source, error) { 66 informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil) 67 virtualServerInformer := informerFactory.ForResource(f5VirtualServerGVR) 68 69 virtualServerInformer.Informer().AddEventHandler( 70 cache.ResourceEventHandlerFuncs{ 71 AddFunc: func(obj interface{}) { 72 }, 73 }, 74 ) 75 76 informerFactory.Start(ctx.Done()) 77 78 // wait for the local cache to be populated. 79 if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil { 80 return nil, err 81 } 82 83 uc, err := newVSUnstructuredConverter() 84 if err != nil { 85 return nil, errors.Wrapf(err, "failed to setup unstructured converter") 86 } 87 88 return &f5VirtualServerSource{ 89 dynamicKubeClient: dynamicKubeClient, 90 virtualServerInformer: virtualServerInformer, 91 kubeClient: kubeClient, 92 namespace: namespace, 93 annotationFilter: annotationFilter, 94 unstructuredConverter: uc, 95 }, nil 96 } 97 98 // Endpoints returns endpoint objects for each host-target combination that should be processed. 99 // Retrieves all VirtualServers in the source's namespace(s). 100 func (vs *f5VirtualServerSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { 101 virtualServerObjects, err := vs.virtualServerInformer.Lister().ByNamespace(vs.namespace).List(labels.Everything()) 102 if err != nil { 103 return nil, err 104 } 105 106 var virtualServers []*f5.VirtualServer 107 for _, vsObj := range virtualServerObjects { 108 unstructuredHost, ok := vsObj.(*unstructured.Unstructured) 109 if !ok { 110 return nil, errors.New("could not convert") 111 } 112 113 virtualServer := &f5.VirtualServer{} 114 err := vs.unstructuredConverter.scheme.Convert(unstructuredHost, virtualServer, nil) 115 if err != nil { 116 return nil, err 117 } 118 virtualServers = append(virtualServers, virtualServer) 119 } 120 121 virtualServers, err = vs.filterByAnnotations(virtualServers) 122 if err != nil { 123 return nil, errors.Wrap(err, "failed to filter VirtualServers") 124 } 125 126 endpoints, err := vs.endpointsFromVirtualServers(virtualServers) 127 if err != nil { 128 return nil, err 129 } 130 131 // Sort endpoints 132 for _, ep := range endpoints { 133 sort.Sort(ep.Targets) 134 } 135 136 return endpoints, nil 137 } 138 139 func (vs *f5VirtualServerSource) AddEventHandler(ctx context.Context, handler func()) { 140 log.Debug("Adding event handler for VirtualServer") 141 142 vs.virtualServerInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) 143 } 144 145 // endpointsFromVirtualServers extracts the endpoints from a slice of VirtualServers 146 func (vs *f5VirtualServerSource) endpointsFromVirtualServers(virtualServers []*f5.VirtualServer) ([]*endpoint.Endpoint, error) { 147 var endpoints []*endpoint.Endpoint 148 149 for _, virtualServer := range virtualServers { 150 resource := fmt.Sprintf("f5-virtualserver/%s/%s", virtualServer.Namespace, virtualServer.Name) 151 152 ttl := getTTLFromAnnotations(virtualServer.Annotations, resource) 153 154 targets := getTargetsFromTargetAnnotation(virtualServer.Annotations) 155 if len(targets) == 0 && virtualServer.Spec.VirtualServerAddress != "" { 156 targets = append(targets, virtualServer.Spec.VirtualServerAddress) 157 } 158 if len(targets) == 0 && virtualServer.Status.VSAddress != "" { 159 targets = append(targets, virtualServer.Status.VSAddress) 160 } 161 162 endpoints = append(endpoints, endpointsForHostname(virtualServer.Spec.Host, targets, ttl, nil, "", resource)...) 163 } 164 165 return endpoints, nil 166 } 167 168 // newUnstructuredConverter returns a new unstructuredConverter initialized 169 func newVSUnstructuredConverter() (*unstructuredConverter, error) { 170 uc := &unstructuredConverter{ 171 scheme: runtime.NewScheme(), 172 } 173 174 // Add the core types we need 175 uc.scheme.AddKnownTypes(f5VirtualServerGVR.GroupVersion(), &f5.VirtualServer{}, &f5.VirtualServerList{}) 176 if err := scheme.AddToScheme(uc.scheme); err != nil { 177 return nil, err 178 } 179 180 return uc, nil 181 } 182 183 // filterByAnnotations filters a list of VirtualServers by a given annotation selector. 184 func (vs *f5VirtualServerSource) filterByAnnotations(virtualServers []*f5.VirtualServer) ([]*f5.VirtualServer, error) { 185 labelSelector, err := metav1.ParseToLabelSelector(vs.annotationFilter) 186 if err != nil { 187 return nil, err 188 } 189 190 selector, err := metav1.LabelSelectorAsSelector(labelSelector) 191 if err != nil { 192 return nil, err 193 } 194 195 // empty filter returns original list 196 if selector.Empty() { 197 return virtualServers, nil 198 } 199 200 filteredList := []*f5.VirtualServer{} 201 202 for _, vs := range virtualServers { 203 // convert the VirtualServer's annotations to an equivalent label selector 204 annotations := labels.Set(vs.Annotations) 205 206 // include VirtualServer if its annotations match the selector 207 if selector.Matches(annotations) { 208 filteredList = append(filteredList, vs) 209 } 210 } 211 212 return filteredList, nil 213 }