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 }