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  }