sigs.k8s.io/external-dns@v0.14.1/source/gloo_proxy.go (about)

     1  /*
     2  Copyright 2020n 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  	"encoding/json"
    22  	"fmt"
    23  	"strings"
    24  
    25  	log "github.com/sirupsen/logrus"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/client-go/dynamic"
    30  	"k8s.io/client-go/kubernetes"
    31  
    32  	"sigs.k8s.io/external-dns/endpoint"
    33  )
    34  
    35  var (
    36  	proxyGVR = schema.GroupVersionResource{
    37  		Group:    "gloo.solo.io",
    38  		Version:  "v1",
    39  		Resource: "proxies",
    40  	}
    41  	virtualServiceGVR = schema.GroupVersionResource{
    42  		Group:    "gateway.solo.io",
    43  		Version:  "v1",
    44  		Resource: "virtualservices",
    45  	}
    46  )
    47  
    48  // Basic redefinition of "Proxy" CRD : https://github.com/solo-io/gloo/blob/v1.4.6/projects/gloo/pkg/api/v1/proxy.pb.go
    49  type proxy struct {
    50  	metav1.TypeMeta `json:",inline"`
    51  	Metadata        metav1.ObjectMeta `json:"metadata,omitempty"`
    52  	Spec            proxySpec         `json:"spec,omitempty"`
    53  }
    54  
    55  type proxySpec struct {
    56  	Listeners []proxySpecListener `json:"listeners,omitempty"`
    57  }
    58  
    59  type proxySpecListener struct {
    60  	HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
    61  }
    62  
    63  type proxySpecHTTPListener struct {
    64  	VirtualHosts []proxyVirtualHost `json:"virtualHosts,omitempty"`
    65  }
    66  
    67  type proxyVirtualHost struct {
    68  	Domains        []string                       `json:"domains,omitempty"`
    69  	Metadata       proxyVirtualHostMetadata       `json:"metadata,omitempty"`
    70  	MetadataStatic proxyVirtualHostMetadataStatic `json:"metadataStatic,omitempty"`
    71  }
    72  
    73  type proxyVirtualHostMetadata struct {
    74  	Source []proxyVirtualHostMetadataSource `json:"sources,omitempty"`
    75  }
    76  
    77  type proxyVirtualHostMetadataStatic struct {
    78  	Source []proxyVirtualHostMetadataStaticSource `json:"sources,omitempty"`
    79  }
    80  
    81  type proxyVirtualHostMetadataSource struct {
    82  	Kind      string `json:"kind,omitempty"`
    83  	Name      string `json:"name,omitempty"`
    84  	Namespace string `json:"namespace,omitempty"`
    85  }
    86  
    87  type proxyVirtualHostMetadataStaticSource struct {
    88  	ResourceKind string                                    `json:"resourceKind,omitempty"`
    89  	ResourceRef  proxyVirtualHostMetadataSourceResourceRef `json:"resourceRef,omitempty"`
    90  }
    91  
    92  type proxyVirtualHostMetadataSourceResourceRef struct {
    93  	Name      string `json:"name,omitempty"`
    94  	Namespace string `json:"namespace,omitempty"`
    95  }
    96  
    97  type glooSource struct {
    98  	dynamicKubeClient dynamic.Interface
    99  	kubeClient        kubernetes.Interface
   100  	glooNamespaces    []string
   101  }
   102  
   103  // NewGlooSource creates a new glooSource with the given config
   104  func NewGlooSource(dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface,
   105  	glooNamespaces []string) (Source, error) {
   106  	return &glooSource{
   107  		dynamicKubeClient,
   108  		kubeClient,
   109  		glooNamespaces,
   110  	}, nil
   111  }
   112  
   113  func (gs *glooSource) AddEventHandler(ctx context.Context, handler func()) {
   114  }
   115  
   116  // Endpoints returns endpoint objects
   117  func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
   118  	endpoints := []*endpoint.Endpoint{}
   119  
   120  	for _, ns := range gs.glooNamespaces {
   121  		proxies, err := gs.dynamicKubeClient.Resource(proxyGVR).Namespace(ns).List(ctx, metav1.ListOptions{})
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  		for _, obj := range proxies.Items {
   126  			proxy := proxy{}
   127  			jsonString, err := obj.MarshalJSON()
   128  			if err != nil {
   129  				return nil, err
   130  			}
   131  			err = json.Unmarshal(jsonString, &proxy)
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  			log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name)
   136  
   137  			proxyTargets := getTargetsFromTargetAnnotation(proxy.Metadata.Annotations)
   138  			if len(proxyTargets) == 0 {
   139  				proxyTargets, err = gs.proxyTargets(ctx, proxy.Metadata.Name, ns)
   140  				if err != nil {
   141  					return nil, err
   142  				}
   143  			}
   144  			log.Debugf("Gloo[%s]: Find %d target(s) (%+v)", proxy.Metadata.Name, len(proxyTargets), proxyTargets)
   145  
   146  			proxyEndpoints, err := gs.generateEndpointsFromProxy(ctx, &proxy, proxyTargets)
   147  			if err != nil {
   148  				return nil, err
   149  			}
   150  			log.Debugf("Gloo[%s]: Generate %d endpoint(s)", proxy.Metadata.Name, len(proxyEndpoints))
   151  			endpoints = append(endpoints, proxyEndpoints...)
   152  		}
   153  	}
   154  	return endpoints, nil
   155  }
   156  
   157  func (gs *glooSource) generateEndpointsFromProxy(ctx context.Context, proxy *proxy, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
   158  	endpoints := []*endpoint.Endpoint{}
   159  
   160  	resource := fmt.Sprintf("proxy/%s/%s", proxy.Metadata.Namespace, proxy.Metadata.Name)
   161  
   162  	for _, listener := range proxy.Spec.Listeners {
   163  		for _, virtualHost := range listener.HTTPListener.VirtualHosts {
   164  			annotations, err := gs.annotationsFromProxySource(ctx, virtualHost)
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  			ttl := getTTLFromAnnotations(annotations, resource)
   169  			providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations)
   170  			for _, domain := range virtualHost.Domains {
   171  				endpoints = append(endpoints, endpointsForHostname(strings.TrimSuffix(domain, "."), targets, ttl, providerSpecific, setIdentifier, "")...)
   172  			}
   173  		}
   174  	}
   175  	return endpoints, nil
   176  }
   177  
   178  func (gs *glooSource) annotationsFromProxySource(ctx context.Context, virtualHost proxyVirtualHost) (map[string]string, error) {
   179  	annotations := map[string]string{}
   180  	for _, src := range virtualHost.Metadata.Source {
   181  		kind := sourceKind(src.Kind)
   182  		if kind != nil {
   183  			source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.Namespace).Get(ctx, src.Name, metav1.GetOptions{})
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  			for key, value := range source.GetAnnotations() {
   188  				annotations[key] = value
   189  			}
   190  		}
   191  	}
   192  	for _, src := range virtualHost.MetadataStatic.Source {
   193  		kind := sourceKind(src.ResourceKind)
   194  		if kind != nil {
   195  			source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.ResourceRef.Namespace).Get(ctx, src.ResourceRef.Name, metav1.GetOptions{})
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  			for key, value := range source.GetAnnotations() {
   200  				annotations[key] = value
   201  			}
   202  		}
   203  	}
   204  	return annotations, nil
   205  }
   206  
   207  func (gs *glooSource) proxyTargets(ctx context.Context, name string, namespace string) (endpoint.Targets, error) {
   208  	svc, err := gs.kubeClient.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	var targets endpoint.Targets
   214  	switch svc.Spec.Type {
   215  	case corev1.ServiceTypeLoadBalancer:
   216  		for _, lb := range svc.Status.LoadBalancer.Ingress {
   217  			if lb.IP != "" {
   218  				targets = append(targets, lb.IP)
   219  			}
   220  			if lb.Hostname != "" {
   221  				targets = append(targets, lb.Hostname)
   222  			}
   223  		}
   224  	default:
   225  		log.WithField("gateway", name).WithField("service", svc).Warn("Gloo: Proxy service type not supported")
   226  	}
   227  	return targets, nil
   228  }
   229  
   230  func sourceKind(kind string) *schema.GroupVersionResource {
   231  	switch kind {
   232  	case "*v1.VirtualService":
   233  		return &virtualServiceGVR
   234  	}
   235  	return nil
   236  }