sigs.k8s.io/external-dns@v0.14.1/source/kong_tcpingress.go (about) 1 /* 2 Copyright 2021 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 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/client-go/dynamic" 33 "k8s.io/client-go/dynamic/dynamicinformer" 34 "k8s.io/client-go/informers" 35 "k8s.io/client-go/kubernetes" 36 "k8s.io/client-go/kubernetes/scheme" 37 "k8s.io/client-go/tools/cache" 38 39 "sigs.k8s.io/external-dns/endpoint" 40 ) 41 42 var kongGroupdVersionResource = schema.GroupVersionResource{ 43 Group: "configuration.konghq.com", 44 Version: "v1beta1", 45 Resource: "tcpingresses", 46 } 47 48 // kongTCPIngressSource is an implementation of Source for Kong TCPIngress objects. 49 type kongTCPIngressSource struct { 50 annotationFilter string 51 ignoreHostnameAnnotation bool 52 dynamicKubeClient dynamic.Interface 53 kongTCPIngressInformer informers.GenericInformer 54 kubeClient kubernetes.Interface 55 namespace string 56 unstructuredConverter *unstructuredConverter 57 } 58 59 // NewKongTCPIngressSource creates a new kongTCPIngressSource with the given config. 60 func NewKongTCPIngressSource(ctx context.Context, dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, namespace string, annotationFilter string, ignoreHostnameAnnotation bool) (Source, error) { 61 var err error 62 63 // Use shared informer to listen for add/update/delete of Host in the specified namespace. 64 // Set resync period to 0, to prevent processing when nothing has changed. 65 informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil) 66 kongTCPIngressInformer := informerFactory.ForResource(kongGroupdVersionResource) 67 68 // Add default resource event handlers to properly initialize informer. 69 kongTCPIngressInformer.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 := newKongUnstructuredConverter() 84 if err != nil { 85 return nil, errors.Wrapf(err, "failed to setup Unstructured Converter") 86 } 87 88 return &kongTCPIngressSource{ 89 annotationFilter: annotationFilter, 90 ignoreHostnameAnnotation: ignoreHostnameAnnotation, 91 dynamicKubeClient: dynamicKubeClient, 92 kongTCPIngressInformer: kongTCPIngressInformer, 93 kubeClient: kubeClient, 94 namespace: namespace, 95 unstructuredConverter: uc, 96 }, nil 97 } 98 99 // Endpoints returns endpoint objects for each host-target combination that should be processed. 100 // Retrieves all TCPIngresses in the source's namespace(s). 101 func (sc *kongTCPIngressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { 102 tis, err := sc.kongTCPIngressInformer.Lister().ByNamespace(sc.namespace).List(labels.Everything()) 103 if err != nil { 104 return nil, err 105 } 106 107 var tcpIngresses []*TCPIngress 108 for _, tcpIngressObj := range tis { 109 unstructuredHost, ok := tcpIngressObj.(*unstructured.Unstructured) 110 if !ok { 111 return nil, errors.New("could not convert") 112 } 113 114 tcpIngress := &TCPIngress{} 115 err := sc.unstructuredConverter.scheme.Convert(unstructuredHost, tcpIngress, nil) 116 if err != nil { 117 return nil, err 118 } 119 tcpIngresses = append(tcpIngresses, tcpIngress) 120 } 121 122 tcpIngresses, err = sc.filterByAnnotations(tcpIngresses) 123 if err != nil { 124 return nil, errors.Wrap(err, "failed to filter TCPIngresses") 125 } 126 127 var endpoints []*endpoint.Endpoint 128 for _, tcpIngress := range tcpIngresses { 129 targets := getTargetsFromTargetAnnotation(tcpIngress.Annotations) 130 if len(targets) == 0 { 131 for _, lb := range tcpIngress.Status.LoadBalancer.Ingress { 132 if lb.IP != "" { 133 targets = append(targets, lb.IP) 134 } 135 if lb.Hostname != "" { 136 targets = append(targets, lb.Hostname) 137 } 138 } 139 } 140 141 fullname := fmt.Sprintf("%s/%s", tcpIngress.Namespace, tcpIngress.Name) 142 143 ingressEndpoints, err := sc.endpointsFromTCPIngress(tcpIngress, targets) 144 if err != nil { 145 return nil, err 146 } 147 if len(ingressEndpoints) == 0 { 148 log.Debugf("No endpoints could be generated from Host %s", fullname) 149 continue 150 } 151 152 log.Debugf("Endpoints generated from TCPIngress: %s: %v", fullname, ingressEndpoints) 153 sc.setDualstackLabel(tcpIngress, ingressEndpoints) 154 endpoints = append(endpoints, ingressEndpoints...) 155 } 156 157 for _, ep := range endpoints { 158 sort.Sort(ep.Targets) 159 } 160 161 return endpoints, nil 162 } 163 164 // filterByAnnotations filters a list of TCPIngresses by a given annotation selector. 165 func (sc *kongTCPIngressSource) filterByAnnotations(tcpIngresses []*TCPIngress) ([]*TCPIngress, error) { 166 labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter) 167 if err != nil { 168 return nil, err 169 } 170 selector, err := metav1.LabelSelectorAsSelector(labelSelector) 171 if err != nil { 172 return nil, err 173 } 174 175 // empty filter returns original list 176 if selector.Empty() { 177 return tcpIngresses, nil 178 } 179 180 filteredList := []*TCPIngress{} 181 182 for _, tcpIngress := range tcpIngresses { 183 // convert the TCPIngress's annotations to an equivalent label selector 184 annotations := labels.Set(tcpIngress.Annotations) 185 186 // include TCPIngress if its annotations match the selector 187 if selector.Matches(annotations) { 188 filteredList = append(filteredList, tcpIngress) 189 } 190 } 191 192 return filteredList, nil 193 } 194 195 func (sc *kongTCPIngressSource) setDualstackLabel(tcpIngress *TCPIngress, endpoints []*endpoint.Endpoint) { 196 val, ok := tcpIngress.Annotations[ALBDualstackAnnotationKey] 197 if ok && val == ALBDualstackAnnotationValue { 198 log.Debugf("Adding dualstack label to TCPIngress %s/%s.", tcpIngress.Namespace, tcpIngress.Name) 199 for _, ep := range endpoints { 200 ep.Labels[endpoint.DualstackLabelKey] = "true" 201 } 202 } 203 } 204 205 // endpointsFromTCPIngress extracts the endpoints from a TCPIngress object 206 func (sc *kongTCPIngressSource) endpointsFromTCPIngress(tcpIngress *TCPIngress, targets endpoint.Targets) ([]*endpoint.Endpoint, error) { 207 var endpoints []*endpoint.Endpoint 208 209 resource := fmt.Sprintf("tcpingress/%s/%s", tcpIngress.Namespace, tcpIngress.Name) 210 211 ttl := getTTLFromAnnotations(tcpIngress.Annotations, resource) 212 213 providerSpecific, setIdentifier := getProviderSpecificAnnotations(tcpIngress.Annotations) 214 215 if !sc.ignoreHostnameAnnotation { 216 hostnameList := getHostnamesFromAnnotations(tcpIngress.Annotations) 217 for _, hostname := range hostnameList { 218 endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...) 219 } 220 } 221 222 if tcpIngress.Spec.Rules != nil { 223 for _, rule := range tcpIngress.Spec.Rules { 224 if rule.Host != "" { 225 endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific, setIdentifier, resource)...) 226 } 227 } 228 } 229 230 return endpoints, nil 231 } 232 233 func (sc *kongTCPIngressSource) AddEventHandler(ctx context.Context, handler func()) { 234 log.Debug("Adding event handler for TCPIngress") 235 236 // Right now there is no way to remove event handler from informer, see: 237 // https://github.com/kubernetes/kubernetes/issues/79610 238 sc.kongTCPIngressInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) 239 } 240 241 // newUnstructuredConverter returns a new unstructuredConverter initialized 242 func newKongUnstructuredConverter() (*unstructuredConverter, error) { 243 uc := &unstructuredConverter{ 244 scheme: runtime.NewScheme(), 245 } 246 247 // Add the core types we need 248 uc.scheme.AddKnownTypes(kongGroupdVersionResource.GroupVersion(), &TCPIngress{}, &TCPIngressList{}) 249 if err := scheme.AddToScheme(uc.scheme); err != nil { 250 return nil, err 251 } 252 253 return uc, nil 254 } 255 256 // Kong types based on https://github.com/Kong/kubernetes-ingress-controller/blob/v1.2.0/pkg/apis/configuration/v1beta1/types.go to facilitate testing 257 // When trying to import them from the Kong repo as a dependency it required upgrading the k8s.io/client-go and k8s.io/apimachinery which seemed 258 // cause several changes in how the mock clients were working that resulted in a bunch of failures in other tests 259 // If that is dealt with at some point the below can be removed and replaced with an actual import 260 type TCPIngress struct { 261 metav1.TypeMeta `json:",inline"` 262 metav1.ObjectMeta `json:"metadata,omitempty"` 263 264 Spec tcpIngressSpec `json:"spec,omitempty"` 265 Status tcpIngressStatus `json:"status,omitempty"` 266 } 267 268 type TCPIngressList struct { 269 metav1.TypeMeta `json:",inline"` 270 metav1.ListMeta `json:"metadata,omitempty"` 271 Items []TCPIngress `json:"items"` 272 } 273 274 type tcpIngressSpec struct { 275 Rules []tcpIngressRule `json:"rules,omitempty"` 276 TLS []tcpIngressTLS `json:"tls,omitempty"` 277 } 278 279 type tcpIngressTLS struct { 280 Hosts []string `json:"hosts,omitempty"` 281 SecretName string `json:"secretName,omitempty"` 282 } 283 284 type tcpIngressStatus struct { 285 LoadBalancer corev1.LoadBalancerStatus `json:"loadBalancer,omitempty"` 286 } 287 288 type tcpIngressRule struct { 289 Host string `json:"host,omitempty"` 290 Port int `json:"port,omitempty"` 291 Backend tcpIngressBackend `json:"backend"` 292 } 293 294 type tcpIngressBackend struct { 295 ServiceName string `json:"serviceName"` 296 ServicePort int `json:"servicePort"` 297 } 298 299 func (in *tcpIngressBackend) DeepCopyInto(out *tcpIngressBackend) { 300 *out = *in 301 } 302 303 func (in *tcpIngressBackend) DeepCopy() *tcpIngressBackend { 304 if in == nil { 305 return nil 306 } 307 out := new(tcpIngressBackend) 308 in.DeepCopyInto(out) 309 return out 310 } 311 312 func (in *tcpIngressRule) DeepCopyInto(out *tcpIngressRule) { 313 *out = *in 314 out.Backend = in.Backend 315 } 316 317 func (in *tcpIngressRule) DeepCopy() *tcpIngressRule { 318 if in == nil { 319 return nil 320 } 321 out := new(tcpIngressRule) 322 in.DeepCopyInto(out) 323 return out 324 } 325 326 func (in *tcpIngressSpec) DeepCopyInto(out *tcpIngressSpec) { 327 *out = *in 328 if in.Rules != nil { 329 in, out := &in.Rules, &out.Rules 330 *out = make([]tcpIngressRule, len(*in)) 331 copy(*out, *in) 332 } 333 if in.TLS != nil { 334 in, out := &in.TLS, &out.TLS 335 *out = make([]tcpIngressTLS, len(*in)) 336 for i := range *in { 337 (*in)[i].DeepCopyInto(&(*out)[i]) 338 } 339 } 340 } 341 342 func (in *tcpIngressSpec) DeepCopy() *tcpIngressSpec { 343 if in == nil { 344 return nil 345 } 346 out := new(tcpIngressSpec) 347 in.DeepCopyInto(out) 348 return out 349 } 350 351 func (in *tcpIngressStatus) DeepCopyInto(out *tcpIngressStatus) { 352 *out = *in 353 in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) 354 } 355 356 func (in *tcpIngressStatus) DeepCopy() *tcpIngressStatus { 357 if in == nil { 358 return nil 359 } 360 out := new(tcpIngressStatus) 361 in.DeepCopyInto(out) 362 return out 363 } 364 365 func (in *tcpIngressTLS) DeepCopyInto(out *tcpIngressTLS) { 366 *out = *in 367 if in.Hosts != nil { 368 in, out := &in.Hosts, &out.Hosts 369 *out = make([]string, len(*in)) 370 copy(*out, *in) 371 } 372 } 373 374 func (in *tcpIngressTLS) DeepCopy() *tcpIngressTLS { 375 if in == nil { 376 return nil 377 } 378 out := new(tcpIngressTLS) 379 in.DeepCopyInto(out) 380 return out 381 } 382 383 func (in *TCPIngress) DeepCopyInto(out *TCPIngress) { 384 *out = *in 385 out.TypeMeta = in.TypeMeta 386 in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 387 in.Spec.DeepCopyInto(&out.Spec) 388 in.Status.DeepCopyInto(&out.Status) 389 } 390 391 func (in *TCPIngress) DeepCopy() *TCPIngress { 392 if in == nil { 393 return nil 394 } 395 out := new(TCPIngress) 396 in.DeepCopyInto(out) 397 return out 398 } 399 400 func (in *TCPIngress) DeepCopyObject() runtime.Object { 401 if c := in.DeepCopy(); c != nil { 402 return c 403 } 404 return nil 405 } 406 407 func (in *TCPIngressList) DeepCopyInto(out *TCPIngressList) { 408 *out = *in 409 out.TypeMeta = in.TypeMeta 410 in.ListMeta.DeepCopyInto(&out.ListMeta) 411 if in.Items != nil { 412 in, out := &in.Items, &out.Items 413 *out = make([]TCPIngress, len(*in)) 414 for i := range *in { 415 (*in)[i].DeepCopyInto(&(*out)[i]) 416 } 417 } 418 } 419 420 func (in *TCPIngressList) DeepCopy() *TCPIngressList { 421 if in == nil { 422 return nil 423 } 424 out := new(TCPIngressList) 425 in.DeepCopyInto(out) 426 return out 427 } 428 429 func (in *TCPIngressList) DeepCopyObject() runtime.Object { 430 if c := in.DeepCopy(); c != nil { 431 return c 432 } 433 return nil 434 }