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

     1  /*
     2  Copyright 2017 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  	"strings"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/labels"
    24  
    25  	"sigs.k8s.io/external-dns/endpoint"
    26  )
    27  
    28  const (
    29  	mateAnnotationKey     = "zalando.org/dnsname"
    30  	moleculeAnnotationKey = "domainName"
    31  	// kopsDNSControllerHostnameAnnotationKey is the annotation used for defining the desired hostname when kOps DNS controller compatibility mode
    32  	kopsDNSControllerHostnameAnnotationKey = "dns.alpha.kubernetes.io/external"
    33  	// kopsDNSControllerInternalHostnameAnnotationKey is the annotation used for defining the desired hostname when kOps DNS controller compatibility mode
    34  	kopsDNSControllerInternalHostnameAnnotationKey = "dns.alpha.kubernetes.io/internal"
    35  )
    36  
    37  // legacyEndpointsFromService tries to retrieve Endpoints from Services
    38  // annotated with legacy annotations.
    39  func legacyEndpointsFromService(svc *v1.Service, sc *serviceSource) ([]*endpoint.Endpoint, error) {
    40  	switch sc.compatibility {
    41  	case "mate":
    42  		return legacyEndpointsFromMateService(svc), nil
    43  	case "molecule":
    44  		return legacyEndpointsFromMoleculeService(svc), nil
    45  	case "kops-dns-controller":
    46  		return legacyEndpointsFromDNSControllerService(svc, sc)
    47  	}
    48  
    49  	return []*endpoint.Endpoint{}, nil
    50  }
    51  
    52  // legacyEndpointsFromMateService tries to retrieve Endpoints from Services
    53  // annotated with Mate's annotation semantics.
    54  func legacyEndpointsFromMateService(svc *v1.Service) []*endpoint.Endpoint {
    55  	var endpoints []*endpoint.Endpoint
    56  
    57  	// Get the desired hostname of the service from the annotation.
    58  	hostname, exists := svc.Annotations[mateAnnotationKey]
    59  	if !exists {
    60  		return nil
    61  	}
    62  
    63  	// Create a corresponding endpoint for each configured external entrypoint.
    64  	for _, lb := range svc.Status.LoadBalancer.Ingress {
    65  		if lb.IP != "" {
    66  			endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
    67  		}
    68  		if lb.Hostname != "" {
    69  			endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
    70  		}
    71  	}
    72  
    73  	return endpoints
    74  }
    75  
    76  // legacyEndpointsFromMoleculeService tries to retrieve Endpoints from Services
    77  // annotated with Molecule Software's annotation semantics.
    78  func legacyEndpointsFromMoleculeService(svc *v1.Service) []*endpoint.Endpoint {
    79  	var endpoints []*endpoint.Endpoint
    80  
    81  	// Check that the Service opted-in to being processed.
    82  	if svc.Labels["dns"] != "route53" {
    83  		return nil
    84  	}
    85  
    86  	// Get the desired hostname of the service from the annotation.
    87  	hostnameAnnotation, exists := svc.Annotations[moleculeAnnotationKey]
    88  	if !exists {
    89  		return nil
    90  	}
    91  
    92  	hostnameList := strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")
    93  
    94  	for _, hostname := range hostnameList {
    95  		// Create a corresponding endpoint for each configured external entrypoint.
    96  		for _, lb := range svc.Status.LoadBalancer.Ingress {
    97  			if lb.IP != "" {
    98  				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
    99  			}
   100  			if lb.Hostname != "" {
   101  				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
   102  			}
   103  		}
   104  	}
   105  
   106  	return endpoints
   107  }
   108  
   109  // legacyEndpointsFromDNSControllerService tries to retrieve Endpoints from Services
   110  // annotated with DNS Controller's annotation semantics*.
   111  func legacyEndpointsFromDNSControllerService(svc *v1.Service, sc *serviceSource) ([]*endpoint.Endpoint, error) {
   112  	switch svc.Spec.Type {
   113  	case v1.ServiceTypeNodePort:
   114  		return legacyEndpointsFromDNSControllerNodePortService(svc, sc)
   115  	case v1.ServiceTypeLoadBalancer:
   116  		return legacyEndpointsFromDNSControllerLoadBalancerService(svc), nil
   117  	}
   118  
   119  	return []*endpoint.Endpoint{}, nil
   120  }
   121  
   122  // legacyEndpointsFromDNSControllerNodePortService implements DNS controller's semantics for NodePort services.
   123  // It will use node role label to check if the node has the "node" role. This means control plane nodes and other
   124  // roles will not be used as targets.
   125  func legacyEndpointsFromDNSControllerNodePortService(svc *v1.Service, sc *serviceSource) ([]*endpoint.Endpoint, error) {
   126  	var endpoints []*endpoint.Endpoint
   127  
   128  	// Get the desired hostname of the service from the annotations.
   129  	hostnameAnnotation, isExternal := svc.Annotations[kopsDNSControllerHostnameAnnotationKey]
   130  	internalHostnameAnnotation, isInternal := svc.Annotations[kopsDNSControllerInternalHostnameAnnotationKey]
   131  
   132  	if !isExternal && !isInternal {
   133  		return nil, nil
   134  	}
   135  
   136  	// if both annotations are set, we just return empty, mimicking what dns-controller does
   137  	if isInternal && isExternal {
   138  		return nil, nil
   139  	}
   140  
   141  	nodes, err := sc.nodeInformer.Lister().List(labels.Everything())
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	var hostnameList []string
   147  	if isExternal {
   148  		hostnameList = strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")
   149  	} else {
   150  		hostnameList = strings.Split(strings.Replace(internalHostnameAnnotation, " ", "", -1), ",")
   151  	}
   152  
   153  	for _, hostname := range hostnameList {
   154  		for _, node := range nodes {
   155  			_, isNode := node.Labels["node-role.kubernetes.io/node"]
   156  			if !isNode {
   157  				continue
   158  			}
   159  			for _, address := range node.Status.Addresses {
   160  				recordType := suitableType(address.Address)
   161  				// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
   162  				if isExternal && (address.Type == v1.NodeExternalIP || (address.Type == v1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA)) {
   163  					endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address))
   164  				}
   165  				if isInternal && address.Type == v1.NodeInternalIP {
   166  					endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address))
   167  				}
   168  			}
   169  		}
   170  	}
   171  	return endpoints, nil
   172  }
   173  
   174  // legacyEndpointsFromDNSControllerLoadBalancerService will respect both annotations, but
   175  // will not care if the load balancer actually is internal or not.
   176  func legacyEndpointsFromDNSControllerLoadBalancerService(svc *v1.Service) []*endpoint.Endpoint {
   177  	var endpoints []*endpoint.Endpoint
   178  
   179  	// Get the desired hostname of the service from the annotations.
   180  	hostnameAnnotation, hasExternal := svc.Annotations[kopsDNSControllerHostnameAnnotationKey]
   181  	internalHostnameAnnotation, hasInternal := svc.Annotations[kopsDNSControllerInternalHostnameAnnotationKey]
   182  
   183  	if !hasExternal && !hasInternal {
   184  		return nil
   185  	}
   186  
   187  	var hostnameList []string
   188  	if hasExternal {
   189  		hostnameList = append(hostnameList, strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")...)
   190  	}
   191  	if hasInternal {
   192  		hostnameList = append(hostnameList, strings.Split(strings.Replace(internalHostnameAnnotation, " ", "", -1), ",")...)
   193  	}
   194  
   195  	for _, hostname := range hostnameList {
   196  		// Create a corresponding endpoint for each configured external entrypoint.
   197  		for _, lb := range svc.Status.LoadBalancer.Ingress {
   198  			if lb.IP != "" {
   199  				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
   200  			}
   201  			if lb.Hostname != "" {
   202  				endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
   203  			}
   204  		}
   205  	}
   206  
   207  	return endpoints
   208  }