github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/k8s/configuration.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strings" 8 "sync" 9 10 "github.com/nginxinc/kubernetes-ingress/internal/configs" 11 conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" 12 conf_v1alpha1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1alpha1" 13 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation" 14 networking "k8s.io/api/networking/v1beta1" 15 "k8s.io/apimachinery/pkg/runtime" 16 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 ) 19 20 const ( 21 ingressKind = "Ingress" 22 virtualServerKind = "VirtualServer" 23 virtualServerRouteKind = "VirtualServerRoute" 24 transportServerKind = "TransportServer" 25 ) 26 27 // Operation defines an operation to perform for a resource. 28 type Operation int 29 30 const ( 31 // Delete the config of the resource 32 Delete Operation = iota 33 // AddOrUpdate the config of the resource 34 AddOrUpdate 35 ) 36 37 // Resource represents a configuration resource. 38 // A Resource can be a top level configuration object: 39 // - Regular or Master Ingress 40 // - VirtualServer 41 // - TransportServer 42 type Resource interface { 43 GetObjectMeta() *metav1.ObjectMeta 44 GetKeyWithKind() string 45 Wins(resource Resource) bool 46 AddWarning(warning string) 47 IsEqual(resource Resource) bool 48 } 49 50 func chooseObjectMetaWinner(meta1 *metav1.ObjectMeta, meta2 *metav1.ObjectMeta) bool { 51 if meta1.CreationTimestamp.Equal(&meta2.CreationTimestamp) { 52 return meta1.UID > meta2.UID 53 } 54 55 return meta1.CreationTimestamp.Before(&meta2.CreationTimestamp) 56 } 57 58 // ResourceChange represents a change of the resource that needs to be reflected in the NGINX config. 59 type ResourceChange struct { 60 // Op is an operation that needs be performed on the resource. 61 Op Operation 62 // Resource is the target resource. 63 Resource Resource 64 // Error is the error associated with the resource. 65 Error string 66 } 67 68 // ConfigurationProblem is a problem associated with a configuration object. 69 type ConfigurationProblem struct { 70 // Object is a configuration object. 71 Object runtime.Object 72 // IsError tells if the problem is an error. If it is an error, then it is expected that the status of the object 73 // will be updated to the state 'invalid'. Otherwise, the state will be 'warning'. 74 IsError bool 75 // Reason tells the reason. It matches the reason in the events/status of our configuration objects. 76 Reason string 77 // Messages gives the details about the problem. It matches the message in the events/status of our configuration objects. 78 Message string 79 } 80 81 func compareConfigurationProblems(problem1 *ConfigurationProblem, problem2 *ConfigurationProblem) bool { 82 return problem1.IsError == problem2.IsError && 83 problem1.Reason == problem2.Reason && 84 problem1.Message == problem2.Message 85 } 86 87 // IngressConfiguration holds an Ingress resource with its minions. It implements the Resource interface. 88 type IngressConfiguration struct { 89 // Ingress holds a regular Ingress or a master Ingress. 90 Ingress *networking.Ingress 91 // IsMaster is true when the Ingress is a master. 92 IsMaster bool 93 // Minions contains minions if the Ingress is a master. 94 Minions []*MinionConfiguration 95 // ValidHosts marks the hosts of the Ingress as valid (true) or invalid (false). 96 // Regular Ingress resources can have multiple hosts. It is possible that some of the hosts are taken by other 97 // resources. In that case, those hosts will be marked as invalid. 98 ValidHosts map[string]bool 99 // Warnings includes all the warnings for the resource. 100 Warnings []string 101 // ChildWarnings includes the warnings of the minions. The key is the namespace/name. 102 ChildWarnings map[string][]string 103 } 104 105 // NewRegularIngressConfiguration creates an IngressConfiguration from an Ingress resource. 106 func NewRegularIngressConfiguration(ing *networking.Ingress) *IngressConfiguration { 107 return &IngressConfiguration{ 108 Ingress: ing, 109 IsMaster: false, 110 ValidHosts: make(map[string]bool), 111 ChildWarnings: make(map[string][]string), 112 } 113 } 114 115 // NewMasterIngressConfiguration creates an IngressConfiguration from a master Ingress resource. 116 func NewMasterIngressConfiguration(ing *networking.Ingress, minions []*MinionConfiguration, childWarnings map[string][]string) *IngressConfiguration { 117 return &IngressConfiguration{ 118 Ingress: ing, 119 IsMaster: true, 120 Minions: minions, 121 ValidHosts: make(map[string]bool), 122 ChildWarnings: childWarnings, 123 } 124 } 125 126 // GetObjectMeta returns the resource ObjectMeta. 127 func (ic *IngressConfiguration) GetObjectMeta() *metav1.ObjectMeta { 128 return &ic.Ingress.ObjectMeta 129 } 130 131 // GetKeyWithKind returns the key of the resource with its kind. For example, Ingress/my-namespace/my-name. 132 func (ic *IngressConfiguration) GetKeyWithKind() string { 133 key := getResourceKey(&ic.Ingress.ObjectMeta) 134 return fmt.Sprintf("%s/%s", ingressKind, key) 135 } 136 137 // Wins tells if this resource wins over the specified resource. 138 func (ic *IngressConfiguration) Wins(resource Resource) bool { 139 return chooseObjectMetaWinner(ic.GetObjectMeta(), resource.GetObjectMeta()) 140 } 141 142 // AddWarning adds a warning. 143 func (ic *IngressConfiguration) AddWarning(warning string) { 144 ic.Warnings = append(ic.Warnings, warning) 145 } 146 147 // IsEqual tests if the IngressConfiguration is equal to the resource. 148 func (ic *IngressConfiguration) IsEqual(resource Resource) bool { 149 ingConfig, ok := resource.(*IngressConfiguration) 150 if !ok { 151 return false 152 } 153 154 if !compareObjectMetasWithAnnotations(&ic.Ingress.ObjectMeta, &ingConfig.Ingress.ObjectMeta) { 155 return false 156 } 157 158 if !reflect.DeepEqual(ic.ValidHosts, ingConfig.ValidHosts) { 159 return false 160 } 161 162 if ic.IsMaster != ingConfig.IsMaster { 163 return false 164 } 165 166 if len(ic.Minions) != len(ingConfig.Minions) { 167 return false 168 } 169 170 for i := range ic.Minions { 171 if !compareObjectMetasWithAnnotations(&ic.Minions[i].Ingress.ObjectMeta, &ingConfig.Minions[i].Ingress.ObjectMeta) { 172 return false 173 } 174 } 175 176 return true 177 } 178 179 // MinionConfiguration holds a Minion resource. 180 type MinionConfiguration struct { 181 // Ingress is the Ingress behind a minion. 182 Ingress *networking.Ingress 183 // ValidPaths marks the paths of the Ingress as valid (true) or invalid (false). 184 // Minion Ingress resources can have multiple paths. It is possible that some of the paths are taken by other 185 // Minions. In that case, those paths will be marked as invalid. 186 ValidPaths map[string]bool 187 } 188 189 // NewMinionConfiguration creates a new MinionConfiguration. 190 func NewMinionConfiguration(ing *networking.Ingress) *MinionConfiguration { 191 return &MinionConfiguration{ 192 Ingress: ing, 193 ValidPaths: make(map[string]bool), 194 } 195 } 196 197 // VirtualServerConfiguration holds a VirtualServer along with its VirtualServerRoutes. 198 type VirtualServerConfiguration struct { 199 VirtualServer *conf_v1.VirtualServer 200 VirtualServerRoutes []*conf_v1.VirtualServerRoute 201 Warnings []string 202 } 203 204 // NewVirtualServerConfiguration creates a VirtualServerConfiguration. 205 func NewVirtualServerConfiguration(vs *conf_v1.VirtualServer, vsrs []*conf_v1.VirtualServerRoute, warnings []string) *VirtualServerConfiguration { 206 return &VirtualServerConfiguration{ 207 VirtualServer: vs, 208 VirtualServerRoutes: vsrs, 209 Warnings: warnings, 210 } 211 } 212 213 // GetObjectMeta returns the resource ObjectMeta. 214 func (vsc *VirtualServerConfiguration) GetObjectMeta() *metav1.ObjectMeta { 215 return &vsc.VirtualServer.ObjectMeta 216 } 217 218 // GetKeyWithKind returns the key of the resource with its kind. For example, VirtualServer/my-namespace/my-name. 219 func (vsc *VirtualServerConfiguration) GetKeyWithKind() string { 220 key := getResourceKey(&vsc.VirtualServer.ObjectMeta) 221 return fmt.Sprintf("%s/%s", virtualServerKind, key) 222 } 223 224 // Wins tells if this resource wins over the specified resource. 225 // It is used to determine which resource should win over a host. 226 func (vsc *VirtualServerConfiguration) Wins(resource Resource) bool { 227 return chooseObjectMetaWinner(vsc.GetObjectMeta(), resource.GetObjectMeta()) 228 } 229 230 // AddWarning adds a warning. 231 func (vsc *VirtualServerConfiguration) AddWarning(warning string) { 232 vsc.Warnings = append(vsc.Warnings, warning) 233 } 234 235 // IsEqual tests if the VirtualServerConfiguration is equal to the resource. 236 func (vsc *VirtualServerConfiguration) IsEqual(resource Resource) bool { 237 vsConfig, ok := resource.(*VirtualServerConfiguration) 238 if !ok { 239 return false 240 } 241 242 if !compareObjectMetas(&vsc.VirtualServer.ObjectMeta, &vsConfig.VirtualServer.ObjectMeta) { 243 return false 244 } 245 246 if len(vsc.VirtualServerRoutes) != len(vsConfig.VirtualServerRoutes) { 247 return false 248 } 249 250 for i := range vsc.VirtualServerRoutes { 251 if !compareObjectMetas(&vsc.VirtualServerRoutes[i].ObjectMeta, &vsConfig.VirtualServerRoutes[i].ObjectMeta) { 252 return false 253 } 254 } 255 256 return true 257 } 258 259 // TransportServerConfiguration holds a TransportServer resource. 260 type TransportServerConfiguration struct { 261 ListenerPort int 262 TransportServer *conf_v1alpha1.TransportServer 263 Warnings []string 264 } 265 266 // NewTransportServerConfiguration creates a new TransportServerConfiguration. 267 func NewTransportServerConfiguration(ts *conf_v1alpha1.TransportServer) *TransportServerConfiguration { 268 return &TransportServerConfiguration{ 269 TransportServer: ts, 270 } 271 } 272 273 // GetObjectMeta returns the resource ObjectMeta. 274 func (tsc *TransportServerConfiguration) GetObjectMeta() *metav1.ObjectMeta { 275 return &tsc.TransportServer.ObjectMeta 276 } 277 278 // GetKeyWithKind returns the key of the resource with its kind. For example, TransportServer/my-namespace/my-name. 279 func (tsc *TransportServerConfiguration) GetKeyWithKind() string { 280 key := getResourceKey(&tsc.TransportServer.ObjectMeta) 281 return fmt.Sprintf("%s/%s", transportServerKind, key) 282 } 283 284 // Wins tells if this resource wins over the specified resource. 285 // It is used to determine which resource should win over a host or port. 286 func (tsc *TransportServerConfiguration) Wins(resource Resource) bool { 287 return chooseObjectMetaWinner(tsc.GetObjectMeta(), resource.GetObjectMeta()) 288 } 289 290 // AddWarning adds a warning. 291 func (tsc *TransportServerConfiguration) AddWarning(warning string) { 292 tsc.Warnings = append(tsc.Warnings, warning) 293 } 294 295 // IsEqual tests if the TransportServerConfiguration is equal to the resource. 296 func (tsc *TransportServerConfiguration) IsEqual(resource Resource) bool { 297 tsConfig, ok := resource.(*TransportServerConfiguration) 298 if !ok { 299 return false 300 } 301 302 return compareObjectMetas(tsc.GetObjectMeta(), resource.GetObjectMeta()) && tsc.ListenerPort == tsConfig.ListenerPort 303 } 304 305 func compareObjectMetas(meta1 *metav1.ObjectMeta, meta2 *metav1.ObjectMeta) bool { 306 return meta1.Namespace == meta2.Namespace && 307 meta1.Name == meta2.Name && 308 meta1.Generation == meta2.Generation 309 } 310 311 func compareObjectMetasWithAnnotations(meta1 *metav1.ObjectMeta, meta2 *metav1.ObjectMeta) bool { 312 return compareObjectMetas(meta1, meta2) && reflect.DeepEqual(meta1.Annotations, meta2.Annotations) 313 } 314 315 // Configuration represents the configuration of the Ingress Controller - a collection of configuration objects 316 // (Ingresses, VirtualServers, VirtualServerRoutes) ready to be transformed into NGINX config. 317 // It holds the latest valid state of those objects. 318 // The IC needs to ensure that at any point in time the NGINX config on the filesystem reflects the state 319 // of the objects in the Configuration. 320 type Configuration struct { 321 hosts map[string]Resource 322 listeners map[string]*TransportServerConfiguration 323 324 // only valid resources with the matching IngressClass are stored 325 ingresses map[string]*networking.Ingress 326 virtualServers map[string]*conf_v1.VirtualServer 327 virtualServerRoutes map[string]*conf_v1.VirtualServerRoute 328 transportServers map[string]*conf_v1alpha1.TransportServer 329 330 globalConfiguration *conf_v1alpha1.GlobalConfiguration 331 332 hostProblems map[string]ConfigurationProblem 333 listenerProblems map[string]ConfigurationProblem 334 335 hasCorrectIngressClass func(interface{}) bool 336 virtualServerValidator *validation.VirtualServerValidator 337 globalConfigurationValidator *validation.GlobalConfigurationValidator 338 transportServerValidator *validation.TransportServerValidator 339 340 secretReferenceChecker *secretReferenceChecker 341 serviceReferenceChecker *serviceReferenceChecker 342 endpointReferenceChecker *serviceReferenceChecker 343 policyReferenceChecker *policyReferenceChecker 344 appPolicyReferenceChecker *appProtectResourceReferenceChecker 345 appLogConfReferenceChecker *appProtectResourceReferenceChecker 346 347 isPlus bool 348 appProtectEnabled bool 349 internalRoutesEnabled bool 350 isTLSPassthroughEnabled bool 351 snippetsEnabled bool 352 353 lock sync.RWMutex 354 } 355 356 // NewConfiguration creates a new Configuration. 357 func NewConfiguration( 358 hasCorrectIngressClass func(interface{}) bool, 359 isPlus bool, 360 appProtectEnabled bool, 361 internalRoutesEnabled bool, 362 virtualServerValidator *validation.VirtualServerValidator, 363 globalConfigurationValidator *validation.GlobalConfigurationValidator, 364 transportServerValidator *validation.TransportServerValidator, 365 isTLSPassthroughEnabled bool, 366 snippetsEnabled bool, 367 ) *Configuration { 368 return &Configuration{ 369 hosts: make(map[string]Resource), 370 listeners: make(map[string]*TransportServerConfiguration), 371 ingresses: make(map[string]*networking.Ingress), 372 virtualServers: make(map[string]*conf_v1.VirtualServer), 373 virtualServerRoutes: make(map[string]*conf_v1.VirtualServerRoute), 374 transportServers: make(map[string]*conf_v1alpha1.TransportServer), 375 hostProblems: make(map[string]ConfigurationProblem), 376 hasCorrectIngressClass: hasCorrectIngressClass, 377 virtualServerValidator: virtualServerValidator, 378 globalConfigurationValidator: globalConfigurationValidator, 379 transportServerValidator: transportServerValidator, 380 secretReferenceChecker: newSecretReferenceChecker(isPlus), 381 serviceReferenceChecker: newServiceReferenceChecker(false), 382 endpointReferenceChecker: newServiceReferenceChecker(true), 383 policyReferenceChecker: newPolicyReferenceChecker(), 384 appPolicyReferenceChecker: newAppProtectResourceReferenceChecker(configs.AppProtectPolicyAnnotation), 385 appLogConfReferenceChecker: newAppProtectResourceReferenceChecker(configs.AppProtectLogConfAnnotation), 386 isPlus: isPlus, 387 appProtectEnabled: appProtectEnabled, 388 internalRoutesEnabled: internalRoutesEnabled, 389 isTLSPassthroughEnabled: isTLSPassthroughEnabled, 390 snippetsEnabled: snippetsEnabled, 391 } 392 } 393 394 // AddOrUpdateIngress adds or updates the Ingress resource. 395 func (c *Configuration) AddOrUpdateIngress(ing *networking.Ingress) ([]ResourceChange, []ConfigurationProblem) { 396 c.lock.Lock() 397 defer c.lock.Unlock() 398 399 key := getResourceKey(&ing.ObjectMeta) 400 var validationError error 401 402 if !c.hasCorrectIngressClass(ing) { 403 delete(c.ingresses, key) 404 } else { 405 validationError = validateIngress(ing, c.isPlus, c.appProtectEnabled, c.internalRoutesEnabled, c.snippetsEnabled).ToAggregate() 406 if validationError != nil { 407 delete(c.ingresses, key) 408 } else { 409 c.ingresses[key] = ing 410 } 411 } 412 413 changes, problems := c.rebuildHosts() 414 415 if validationError != nil { 416 // If the invalid resource has any active hosts, rebuildHosts will create a change 417 // to remove the resource. 418 // Here we add the validationErr to that change. 419 keyWithKind := getResourceKeyWithKind(ingressKind, &ing.ObjectMeta) 420 for i := range changes { 421 k := changes[i].Resource.GetKeyWithKind() 422 423 if k == keyWithKind { 424 changes[i].Error = validationError.Error() 425 return changes, problems 426 } 427 } 428 429 // On the other hand, the invalid resource might not have any active hosts. 430 // Or the resource was invalid before and is still invalid (in some different way). 431 // In those cases, rebuildHosts will create no change for that resource. 432 // To make sure the validationErr is reported to the user, we create a problem. 433 p := ConfigurationProblem{ 434 Object: ing, 435 IsError: true, 436 Reason: "Rejected", 437 Message: validationError.Error(), 438 } 439 problems = append(problems, p) 440 } 441 442 return changes, problems 443 } 444 445 // DeleteIngress deletes an Ingress resource by the key. 446 func (c *Configuration) DeleteIngress(key string) ([]ResourceChange, []ConfigurationProblem) { 447 c.lock.Lock() 448 defer c.lock.Unlock() 449 450 _, exists := c.ingresses[key] 451 if !exists { 452 return nil, nil 453 } 454 455 delete(c.ingresses, key) 456 457 return c.rebuildHosts() 458 } 459 460 // AddOrUpdateVirtualServer adds or updates the VirtualServer resource. 461 func (c *Configuration) AddOrUpdateVirtualServer(vs *conf_v1.VirtualServer) ([]ResourceChange, []ConfigurationProblem) { 462 c.lock.Lock() 463 defer c.lock.Unlock() 464 465 key := getResourceKey(&vs.ObjectMeta) 466 var validationError error 467 468 if !c.hasCorrectIngressClass(vs) { 469 delete(c.virtualServers, key) 470 } else { 471 validationError = c.virtualServerValidator.ValidateVirtualServer(vs) 472 if validationError != nil { 473 delete(c.virtualServers, key) 474 } else { 475 c.virtualServers[key] = vs 476 } 477 } 478 479 changes, problems := c.rebuildHosts() 480 481 if validationError != nil { 482 // If the invalid resource has an active host, rebuildHosts will create a change 483 // to remove the resource. 484 // Here we add the validationErr to that change. 485 kind := getResourceKeyWithKind(virtualServerKind, &vs.ObjectMeta) 486 for i := range changes { 487 k := changes[i].Resource.GetKeyWithKind() 488 489 if k == kind { 490 changes[i].Error = validationError.Error() 491 return changes, problems 492 } 493 } 494 495 // On the other hand, the invalid resource might not have any active host. 496 // Or the resource was invalid before and is still invalid (in some different way). 497 // In those cases, rebuildHosts will create no change for that resource. 498 // To make sure the validationErr is reported to the user, we create a problem. 499 p := ConfigurationProblem{ 500 Object: vs, 501 IsError: true, 502 Reason: "Rejected", 503 Message: fmt.Sprintf("VirtualServer %s was rejected with error: %s", getResourceKey(&vs.ObjectMeta), validationError.Error()), 504 } 505 problems = append(problems, p) 506 } 507 508 return changes, problems 509 } 510 511 // DeleteVirtualServer deletes a VirtualServerResource by the key. 512 func (c *Configuration) DeleteVirtualServer(key string) ([]ResourceChange, []ConfigurationProblem) { 513 c.lock.Lock() 514 defer c.lock.Unlock() 515 516 _, exists := c.virtualServers[key] 517 if !exists { 518 return nil, nil 519 } 520 521 delete(c.virtualServers, key) 522 523 return c.rebuildHosts() 524 } 525 526 // AddOrUpdateVirtualServerRoute adds or updates the VirtualServerRoute. 527 func (c *Configuration) AddOrUpdateVirtualServerRoute(vsr *conf_v1.VirtualServerRoute) ([]ResourceChange, []ConfigurationProblem) { 528 c.lock.Lock() 529 defer c.lock.Unlock() 530 531 key := getResourceKey(&vsr.ObjectMeta) 532 var validationError error 533 534 if !c.hasCorrectIngressClass(vsr) { 535 delete(c.virtualServerRoutes, key) 536 } else { 537 validationError = c.virtualServerValidator.ValidateVirtualServerRoute(vsr) 538 if validationError != nil { 539 delete(c.virtualServerRoutes, key) 540 } else { 541 c.virtualServerRoutes[key] = vsr 542 } 543 } 544 545 changes, problems := c.rebuildHosts() 546 547 if validationError != nil { 548 p := ConfigurationProblem{ 549 Object: vsr, 550 IsError: true, 551 Reason: "Rejected", 552 Message: fmt.Sprintf("VirtualServerRoute %s was rejected with error: %s", getResourceKey(&vsr.ObjectMeta), validationError.Error()), 553 } 554 problems = append(problems, p) 555 } 556 557 return changes, problems 558 } 559 560 // DeleteVirtualServerRoute deletes a VirtualServerRoute by the key. 561 func (c *Configuration) DeleteVirtualServerRoute(key string) ([]ResourceChange, []ConfigurationProblem) { 562 c.lock.Lock() 563 defer c.lock.Unlock() 564 565 _, exists := c.virtualServerRoutes[key] 566 if !exists { 567 return nil, nil 568 } 569 570 delete(c.virtualServerRoutes, key) 571 572 return c.rebuildHosts() 573 } 574 575 // AddOrUpdateGlobalConfiguration adds or updates the GlobalConfiguration. 576 func (c *Configuration) AddOrUpdateGlobalConfiguration(gc *conf_v1alpha1.GlobalConfiguration) ([]ResourceChange, []ConfigurationProblem, error) { 577 c.lock.Lock() 578 defer c.lock.Unlock() 579 580 validationErr := c.globalConfigurationValidator.ValidateGlobalConfiguration(gc) 581 if validationErr != nil { 582 c.globalConfiguration = nil 583 } else { 584 c.globalConfiguration = gc 585 } 586 587 changes, problems := c.rebuildListeners() 588 589 return changes, problems, validationErr 590 } 591 592 // DeleteGlobalConfiguration deletes GlobalConfiguration. 593 func (c *Configuration) DeleteGlobalConfiguration() ([]ResourceChange, []ConfigurationProblem) { 594 c.lock.Lock() 595 defer c.lock.Unlock() 596 597 c.globalConfiguration = nil 598 changes, problems := c.rebuildListeners() 599 600 return changes, problems 601 } 602 603 // AddOrUpdateTransportServer adds or updates the TransportServer. 604 func (c *Configuration) AddOrUpdateTransportServer(ts *conf_v1alpha1.TransportServer) ([]ResourceChange, []ConfigurationProblem) { 605 c.lock.Lock() 606 defer c.lock.Unlock() 607 608 key := getResourceKey(&ts.ObjectMeta) 609 var validationErr error 610 611 if !c.hasCorrectIngressClass(ts) { 612 delete(c.transportServers, key) 613 } else { 614 validationErr = c.transportServerValidator.ValidateTransportServer(ts) 615 if validationErr != nil { 616 delete(c.transportServers, key) 617 } else { 618 c.transportServers[key] = ts 619 } 620 } 621 622 changes, problems := c.rebuildListeners() 623 624 if c.isTLSPassthroughEnabled { 625 hostChanges, hostProblems := c.rebuildHosts() 626 627 changes = append(changes, hostChanges...) 628 problems = append(problems, hostProblems...) 629 } 630 631 if validationErr != nil { 632 // If the invalid resource has an active host/listener, rebuildHosts/rebuildListeners will create a change 633 // to remove the resource. 634 // Here we add the validationErr to that change. 635 kind := getResourceKeyWithKind(transportServerKind, &ts.ObjectMeta) 636 for i := range changes { 637 k := changes[i].Resource.GetKeyWithKind() 638 639 if k == kind { 640 changes[i].Error = validationErr.Error() 641 return changes, problems 642 } 643 } 644 645 // On the other hand, the invalid resource might not have any active host/listener. 646 // Or the resource was invalid before and is still invalid (in some different way). 647 // In those cases, rebuildHosts/rebuildListeners will create no change for that resource. 648 // To make sure the validationErr is reported to the user, we create a problem. 649 p := ConfigurationProblem{ 650 Object: ts, 651 IsError: true, 652 Reason: "Rejected", 653 Message: fmt.Sprintf("TransportServer %s was rejected with error: %s", getResourceKey(&ts.ObjectMeta), validationErr.Error()), 654 } 655 problems = append(problems, p) 656 } 657 658 return changes, problems 659 } 660 661 // DeleteTransportServer deletes a TransportServer by the key. 662 func (c *Configuration) DeleteTransportServer(key string) ([]ResourceChange, []ConfigurationProblem) { 663 c.lock.Lock() 664 defer c.lock.Unlock() 665 666 _, exists := c.transportServers[key] 667 if !exists { 668 return nil, nil 669 } 670 671 delete(c.transportServers, key) 672 673 changes, problems := c.rebuildListeners() 674 675 if c.isTLSPassthroughEnabled { 676 hostChanges, hostProblems := c.rebuildHosts() 677 678 changes = append(changes, hostChanges...) 679 problems = append(problems, hostProblems...) 680 } 681 682 return changes, problems 683 } 684 685 func (c *Configuration) rebuildListeners() ([]ResourceChange, []ConfigurationProblem) { 686 newListeners, newTSConfigs := c.buildListenersAndTSConfigurations() 687 688 removedListeners, updatedListeners, addedListeners := detectChangesInListeners(c.listeners, newListeners) 689 changes := createResourceChangesForListeners(removedListeners, updatedListeners, addedListeners, c.listeners, newListeners) 690 691 c.listeners = newListeners 692 693 changes = squashResourceChanges(changes) 694 695 // Note that the change will not refer to the latest version, if the TransportServerConfiguration is being removed. 696 // However, referring to the latest version is necessary so that the resource latest Warnings are reported and not lost. 697 // So here we make sure that changes always refer to the latest version of TransportServerConfigurations. 698 for i := range changes { 699 key := changes[i].Resource.GetKeyWithKind() 700 if r, exists := newTSConfigs[key]; exists { 701 changes[i].Resource = r 702 } 703 } 704 705 newProblems := make(map[string]ConfigurationProblem) 706 707 c.addProblemsForTSConfigsWithoutActiveListener(newTSConfigs, newProblems) 708 709 newOrUpdatedProblems := detectChangesInProblems(newProblems, c.listenerProblems) 710 711 // safe to update problems 712 c.listenerProblems = newProblems 713 714 return changes, newOrUpdatedProblems 715 } 716 717 func (c *Configuration) buildListenersAndTSConfigurations() (newListeners map[string]*TransportServerConfiguration, newTSConfigs map[string]*TransportServerConfiguration) { 718 newListeners = make(map[string]*TransportServerConfiguration) 719 newTSConfigs = make(map[string]*TransportServerConfiguration) 720 721 for key, ts := range c.transportServers { 722 if ts.Spec.Listener.Protocol == conf_v1alpha1.TLSPassthroughListenerProtocol { 723 continue 724 } 725 726 tsc := NewTransportServerConfiguration(ts) 727 newTSConfigs[key] = tsc 728 729 if c.globalConfiguration == nil { 730 continue 731 } 732 733 found := false 734 var listener conf_v1alpha1.Listener 735 for _, l := range c.globalConfiguration.Spec.Listeners { 736 if ts.Spec.Listener.Name == l.Name && ts.Spec.Listener.Protocol == l.Protocol { 737 listener = l 738 found = true 739 break 740 } 741 } 742 743 if !found { 744 continue 745 } 746 747 tsc.ListenerPort = listener.Port 748 749 holder, exists := newListeners[listener.Name] 750 if !exists { 751 newListeners[listener.Name] = tsc 752 continue 753 } 754 755 warning := fmt.Sprintf("listener %s is taken by another resource", listener.Name) 756 757 if !holder.Wins(tsc) { 758 holder.AddWarning(warning) 759 newListeners[listener.Name] = tsc 760 } else { 761 tsc.AddWarning(warning) 762 } 763 } 764 765 return newListeners, newTSConfigs 766 } 767 768 // GetResources returns all configuration resources. 769 func (c *Configuration) GetResources() []Resource { 770 return c.GetResourcesWithFilter(resourceFilter{ 771 Ingresses: true, 772 VirtualServers: true, 773 }) 774 } 775 776 type resourceFilter struct { 777 Ingresses bool 778 VirtualServers bool 779 } 780 781 // GetResourcesWithFilter returns resources using the filter. 782 func (c *Configuration) GetResourcesWithFilter(filter resourceFilter) []Resource { 783 c.lock.RLock() 784 defer c.lock.RUnlock() 785 786 resources := make(map[string]Resource) 787 788 for _, r := range c.hosts { 789 switch r.(type) { 790 case *IngressConfiguration: 791 if filter.Ingresses { 792 resources[r.GetKeyWithKind()] = r 793 } 794 case *VirtualServerConfiguration: 795 if filter.VirtualServers { 796 resources[r.GetKeyWithKind()] = r 797 } 798 } 799 } 800 801 var result []Resource 802 for _, key := range getSortedResourceKeys(resources) { 803 result = append(result, resources[key]) 804 } 805 806 return result 807 } 808 809 // FindResourcesForService finds resources that reference the specified service. 810 func (c *Configuration) FindResourcesForService(svcNamespace string, svcName string) []Resource { 811 return c.findResourcesForResourceReference(svcNamespace, svcName, c.serviceReferenceChecker) 812 } 813 814 // FindResourcesForEndpoints finds resources that reference the specified endpoints. 815 func (c *Configuration) FindResourcesForEndpoints(endpointsNamespace string, endpointsName string) []Resource { 816 // Resources reference not endpoints but the corresponding service, which has the same namespace and name 817 return c.findResourcesForResourceReference(endpointsNamespace, endpointsName, c.endpointReferenceChecker) 818 } 819 820 // FindResourcesForSecret finds resources that reference the specified secret. 821 func (c *Configuration) FindResourcesForSecret(secretNamespace string, secretName string) []Resource { 822 return c.findResourcesForResourceReference(secretNamespace, secretName, c.secretReferenceChecker) 823 } 824 825 // FindResourcesForPolicy finds resources that reference the specified policy. 826 func (c *Configuration) FindResourcesForPolicy(policyNamespace string, policyName string) []Resource { 827 return c.findResourcesForResourceReference(policyNamespace, policyName, c.policyReferenceChecker) 828 } 829 830 // FindResourcesForAppProtectPolicyAnnotation finds resources that reference the specified AppProtect policy via annotation. 831 func (c *Configuration) FindResourcesForAppProtectPolicyAnnotation(policyNamespace string, policyName string) []Resource { 832 return c.findResourcesForResourceReference(policyNamespace, policyName, c.appPolicyReferenceChecker) 833 } 834 835 // FindResourcesForAppProtectLogConfAnnotation finds resources that reference the specified AppProtect LogConf. 836 func (c *Configuration) FindResourcesForAppProtectLogConfAnnotation(logConfNamespace string, logConfName string) []Resource { 837 return c.findResourcesForResourceReference(logConfNamespace, logConfName, c.appLogConfReferenceChecker) 838 } 839 840 func (c *Configuration) findResourcesForResourceReference(namespace string, name string, checker resourceReferenceChecker) []Resource { 841 c.lock.RLock() 842 defer c.lock.RUnlock() 843 844 var result []Resource 845 846 for _, h := range getSortedResourceKeys(c.hosts) { 847 r := c.hosts[h] 848 849 switch impl := r.(type) { 850 case *IngressConfiguration: 851 if checker.IsReferencedByIngress(namespace, name, impl.Ingress) { 852 result = append(result, r) 853 continue 854 } 855 856 for _, fm := range impl.Minions { 857 if checker.IsReferencedByMinion(namespace, name, fm.Ingress) { 858 result = append(result, r) 859 break 860 } 861 } 862 case *VirtualServerConfiguration: 863 if checker.IsReferencedByVirtualServer(namespace, name, impl.VirtualServer) { 864 result = append(result, r) 865 continue 866 } 867 868 for _, vsr := range impl.VirtualServerRoutes { 869 if checker.IsReferencedByVirtualServerRoute(namespace, name, vsr) { 870 result = append(result, r) 871 break 872 } 873 } 874 case *TransportServerConfiguration: 875 if checker.IsReferencedByTransportServer(namespace, name, impl.TransportServer) { 876 result = append(result, r) 877 continue 878 } 879 } 880 } 881 882 for _, l := range getSortedTransportServerConfigurationKeys(c.listeners) { 883 tsConfig := c.listeners[l] 884 885 if checker.IsReferencedByTransportServer(namespace, name, tsConfig.TransportServer) { 886 result = append(result, tsConfig) 887 continue 888 } 889 } 890 891 return result 892 } 893 894 func getResourceKey(meta *metav1.ObjectMeta) string { 895 return fmt.Sprintf("%s/%s", meta.Namespace, meta.Name) 896 } 897 898 // rebuildHosts rebuilds the Configuration and returns the changes to it and the new problems. 899 func (c *Configuration) rebuildHosts() ([]ResourceChange, []ConfigurationProblem) { 900 newHosts, newResources := c.buildHostsAndResources() 901 902 updateActiveHostsForIngresses(newHosts, newResources) 903 904 removedHosts, updatedHosts, addedHosts := detectChangesInHosts(c.hosts, newHosts) 905 changes := createResourceChangesForHosts(removedHosts, updatedHosts, addedHosts, c.hosts, newHosts) 906 907 // safe to update hosts 908 c.hosts = newHosts 909 910 changes = squashResourceChanges(changes) 911 912 // Note that the change will not refer to the latest version, if the resource is being removed. 913 // However, referring to the latest version is necessary so that the resource latest Warnings are reported and not lost. 914 // So here we make sure that changes always refer to the latest version of resources. 915 for i := range changes { 916 key := changes[i].Resource.GetKeyWithKind() 917 if r, exists := newResources[key]; exists { 918 changes[i].Resource = r 919 } 920 } 921 922 newProblems := make(map[string]ConfigurationProblem) 923 924 c.addProblemsForResourcesWithoutActiveHost(newResources, newProblems) 925 c.addProblemsForOrphanMinions(newProblems) 926 c.addProblemsForOrphanOrIgnoredVsrs(newProblems) 927 928 newOrUpdatedProblems := detectChangesInProblems(newProblems, c.hostProblems) 929 930 // safe to update problems 931 c.hostProblems = newProblems 932 933 return changes, newOrUpdatedProblems 934 } 935 936 func updateActiveHostsForIngresses(hosts map[string]Resource, resources map[string]Resource) { 937 for _, r := range resources { 938 ingConfig, ok := r.(*IngressConfiguration) 939 if !ok { 940 continue 941 } 942 943 for _, rule := range ingConfig.Ingress.Spec.Rules { 944 res := hosts[rule.Host] 945 ingConfig.ValidHosts[rule.Host] = res.GetKeyWithKind() == r.GetKeyWithKind() 946 } 947 } 948 } 949 950 func detectChangesInProblems(newProblems map[string]ConfigurationProblem, oldProblems map[string]ConfigurationProblem) []ConfigurationProblem { 951 var result []ConfigurationProblem 952 953 for _, key := range getSortedProblemKeys(newProblems) { 954 newP := newProblems[key] 955 956 oldP, exists := oldProblems[key] 957 if !exists { 958 result = append(result, newP) 959 continue 960 } 961 962 if !compareConfigurationProblems(&newP, &oldP) { 963 result = append(result, newP) 964 } 965 } 966 967 return result 968 } 969 970 func (c *Configuration) addProblemsForTSConfigsWithoutActiveListener(tsConfigs map[string]*TransportServerConfiguration, problems map[string]ConfigurationProblem) { 971 for _, tsc := range tsConfigs { 972 holder, exists := c.listeners[tsc.TransportServer.Spec.Listener.Name] 973 if !exists { 974 p := ConfigurationProblem{ 975 Object: tsc.TransportServer, 976 IsError: false, 977 Reason: "Rejected", 978 Message: fmt.Sprintf("Listener %s doesn't exist", tsc.TransportServer.Spec.Listener.Name), 979 } 980 problems[tsc.GetKeyWithKind()] = p 981 continue 982 } 983 984 if !tsc.IsEqual(holder) { 985 p := ConfigurationProblem{ 986 Object: tsc.TransportServer, 987 IsError: false, 988 Reason: "Rejected", 989 Message: fmt.Sprintf("Listener %s is taken by another resource", tsc.TransportServer.Spec.Listener.Name), 990 } 991 problems[tsc.GetKeyWithKind()] = p 992 } 993 } 994 } 995 996 func (c *Configuration) addProblemsForResourcesWithoutActiveHost(resources map[string]Resource, problems map[string]ConfigurationProblem) { 997 for _, r := range resources { 998 switch impl := r.(type) { 999 case *IngressConfiguration: 1000 atLeastOneValidHost := false 1001 for _, v := range impl.ValidHosts { 1002 if v { 1003 atLeastOneValidHost = true 1004 break 1005 } 1006 } 1007 if !atLeastOneValidHost { 1008 p := ConfigurationProblem{ 1009 Object: impl.Ingress, 1010 IsError: false, 1011 Reason: "Rejected", 1012 Message: "All hosts are taken by other resources", 1013 } 1014 problems[r.GetKeyWithKind()] = p 1015 } 1016 case *VirtualServerConfiguration: 1017 res := c.hosts[impl.VirtualServer.Spec.Host] 1018 1019 if res.GetKeyWithKind() != r.GetKeyWithKind() { 1020 p := ConfigurationProblem{ 1021 Object: impl.VirtualServer, 1022 IsError: false, 1023 Reason: "Rejected", 1024 Message: "Host is taken by another resource", 1025 } 1026 problems[r.GetKeyWithKind()] = p 1027 } 1028 case *TransportServerConfiguration: 1029 res := c.hosts[impl.TransportServer.Spec.Host] 1030 1031 if res.GetKeyWithKind() != r.GetKeyWithKind() { 1032 p := ConfigurationProblem{ 1033 Object: impl.TransportServer, 1034 IsError: false, 1035 Reason: "Rejected", 1036 Message: "Host is taken by another resource", 1037 } 1038 problems[r.GetKeyWithKind()] = p 1039 } 1040 } 1041 } 1042 } 1043 1044 func (c *Configuration) addProblemsForOrphanMinions(problems map[string]ConfigurationProblem) { 1045 for _, key := range getSortedIngressKeys(c.ingresses) { 1046 ing := c.ingresses[key] 1047 1048 if !isMinion(ing) { 1049 continue 1050 } 1051 1052 r, exists := c.hosts[ing.Spec.Rules[0].Host] 1053 ingressConf, ok := r.(*IngressConfiguration) 1054 1055 if !exists || !ok || !ingressConf.IsMaster { 1056 p := ConfigurationProblem{ 1057 Object: ing, 1058 IsError: false, 1059 Reason: "NoIngressMasterFound", 1060 Message: "Ingress master is invalid or doesn't exist", 1061 } 1062 k := getResourceKeyWithKind(ingressKind, &ing.ObjectMeta) 1063 problems[k] = p 1064 } 1065 } 1066 } 1067 1068 func (c *Configuration) addProblemsForOrphanOrIgnoredVsrs(problems map[string]ConfigurationProblem) { 1069 for _, key := range getSortedVirtualServerRouteKeys(c.virtualServerRoutes) { 1070 vsr := c.virtualServerRoutes[key] 1071 1072 r, exists := c.hosts[vsr.Spec.Host] 1073 vsConfig, ok := r.(*VirtualServerConfiguration) 1074 1075 if !exists || !ok { 1076 p := ConfigurationProblem{ 1077 Object: vsr, 1078 IsError: false, 1079 Reason: "NoVirtualServerFound", 1080 Message: "VirtualServer is invalid or doesn't exist", 1081 } 1082 k := getResourceKeyWithKind(virtualServerRouteKind, &vsr.ObjectMeta) 1083 problems[k] = p 1084 continue 1085 } 1086 1087 found := false 1088 for _, v := range vsConfig.VirtualServerRoutes { 1089 if vsr.Namespace == v.Namespace && vsr.Name == v.Name { 1090 found = true 1091 break 1092 } 1093 } 1094 1095 if !found { 1096 p := ConfigurationProblem{ 1097 Object: vsr, 1098 IsError: false, 1099 Reason: "Ignored", 1100 Message: fmt.Sprintf("VirtualServer %s ignores VirtualServerRoute", getResourceKey(&vsConfig.VirtualServer.ObjectMeta)), 1101 } 1102 k := getResourceKeyWithKind(virtualServerRouteKind, &vsr.ObjectMeta) 1103 problems[k] = p 1104 } 1105 } 1106 } 1107 1108 func getResourceKeyWithKind(kind string, objectMeta *metav1.ObjectMeta) string { 1109 return fmt.Sprintf("%s/%s/%s", kind, objectMeta.Namespace, objectMeta.Name) 1110 } 1111 1112 func createResourceChangesForHosts(removedHosts []string, updatedHosts []string, addedHosts []string, oldHosts map[string]Resource, newHosts map[string]Resource) []ResourceChange { 1113 var changes []ResourceChange 1114 var deleteChanges []ResourceChange 1115 1116 for _, h := range removedHosts { 1117 change := ResourceChange{ 1118 Op: Delete, 1119 Resource: oldHosts[h], 1120 } 1121 deleteChanges = append(deleteChanges, change) 1122 } 1123 1124 for _, h := range updatedHosts { 1125 if oldHosts[h].GetKeyWithKind() != newHosts[h].GetKeyWithKind() { 1126 deleteChange := ResourceChange{ 1127 Op: Delete, 1128 Resource: oldHosts[h], 1129 } 1130 deleteChanges = append(deleteChanges, deleteChange) 1131 } 1132 1133 change := ResourceChange{ 1134 Op: AddOrUpdate, 1135 Resource: newHosts[h], 1136 } 1137 changes = append(changes, change) 1138 } 1139 1140 for _, h := range addedHosts { 1141 change := ResourceChange{ 1142 Op: AddOrUpdate, 1143 Resource: newHosts[h], 1144 } 1145 changes = append(changes, change) 1146 } 1147 1148 // We need to ensure that delete changes come first. 1149 // This way an addOrUpdate change, which might include a resource that uses the same host as a resource 1150 // in a delete change, will be processed only after the config of the delete change is removed. 1151 // That will prevent any host collisions in the NGINX config in the state between the changes. 1152 return append(deleteChanges, changes...) 1153 } 1154 1155 func createResourceChangesForListeners(removedListeners []string, updatedListeners []string, addedListeners []string, oldListeners map[string]*TransportServerConfiguration, 1156 newListeners map[string]*TransportServerConfiguration) []ResourceChange { 1157 var changes []ResourceChange 1158 var deleteChanges []ResourceChange 1159 1160 for _, l := range removedListeners { 1161 change := ResourceChange{ 1162 Op: Delete, 1163 Resource: oldListeners[l], 1164 } 1165 deleteChanges = append(deleteChanges, change) 1166 } 1167 1168 for _, l := range updatedListeners { 1169 if oldListeners[l].GetKeyWithKind() != newListeners[l].GetKeyWithKind() { 1170 deleteChange := ResourceChange{ 1171 Op: Delete, 1172 Resource: oldListeners[l], 1173 } 1174 deleteChanges = append(deleteChanges, deleteChange) 1175 } 1176 1177 change := ResourceChange{ 1178 Op: AddOrUpdate, 1179 Resource: newListeners[l], 1180 } 1181 changes = append(changes, change) 1182 } 1183 1184 for _, l := range addedListeners { 1185 change := ResourceChange{ 1186 Op: AddOrUpdate, 1187 Resource: newListeners[l], 1188 } 1189 changes = append(changes, change) 1190 } 1191 1192 // We need to ensure that delete changes come first. 1193 // This way an addOrUpdate change, which might include a resource that uses the same listener as a resource 1194 // in a delete change, will be processed only after the config of the delete change is removed. 1195 // That will prevent any listener collisions in the NGINX config in the state between the changes. 1196 return append(deleteChanges, changes...) 1197 } 1198 1199 func squashResourceChanges(changes []ResourceChange) []ResourceChange { 1200 // deletes for the same resource become a single delete 1201 // updates for the same resource become a single update 1202 // delete and update for the same resource become a single update 1203 1204 var deletes []ResourceChange 1205 var updates []ResourceChange 1206 1207 changesPerResource := make(map[string][]ResourceChange) 1208 1209 for _, c := range changes { 1210 key := c.Resource.GetKeyWithKind() 1211 changesPerResource[key] = append(changesPerResource[key], c) 1212 } 1213 1214 // we range over the changes again to preserver the original order 1215 for _, c := range changes { 1216 key := c.Resource.GetKeyWithKind() 1217 resChanges, exists := changesPerResource[key] 1218 1219 if !exists { 1220 continue 1221 } 1222 1223 // the last element will be an update (if it exists) or a delete 1224 squashedChanged := resChanges[len(resChanges)-1] 1225 if squashedChanged.Op == Delete { 1226 deletes = append(deletes, squashedChanged) 1227 } else { 1228 updates = append(updates, squashedChanged) 1229 } 1230 1231 delete(changesPerResource, key) 1232 } 1233 1234 // We need to ensure that delete changes come first. 1235 // This way an addOrUpdate change, which might include a resource that uses the same host/listener as a resource 1236 // in a delete change, will be processed only after the config of the delete change is removed. 1237 // That will prevent any host/listener collisions in the NGINX config in the state between the changes. 1238 return append(deletes, updates...) 1239 } 1240 1241 func (c *Configuration) buildHostsAndResources() (newHosts map[string]Resource, newResources map[string]Resource) { 1242 newHosts = make(map[string]Resource) 1243 newResources = make(map[string]Resource) 1244 1245 // Step 1 - Build hosts from Ingress resources 1246 1247 for _, key := range getSortedIngressKeys(c.ingresses) { 1248 ing := c.ingresses[key] 1249 1250 if isMinion(ing) { 1251 continue 1252 } 1253 1254 var resource *IngressConfiguration 1255 1256 if isMaster(ing) { 1257 minions, childWarnings := c.buildMinionConfigs(ing.Spec.Rules[0].Host) 1258 resource = NewMasterIngressConfiguration(ing, minions, childWarnings) 1259 } else { 1260 resource = NewRegularIngressConfiguration(ing) 1261 } 1262 1263 newResources[resource.GetKeyWithKind()] = resource 1264 1265 for _, rule := range ing.Spec.Rules { 1266 holder, exists := newHosts[rule.Host] 1267 if !exists { 1268 newHosts[rule.Host] = resource 1269 continue 1270 } 1271 1272 warning := fmt.Sprintf("host %s is taken by another resource", rule.Host) 1273 1274 if !holder.Wins(resource) { 1275 holder.AddWarning(warning) 1276 newHosts[rule.Host] = resource 1277 } else { 1278 resource.AddWarning(warning) 1279 } 1280 } 1281 } 1282 1283 // Step 2 - Build hosts from VirtualServer resources 1284 1285 for _, key := range getSortedVirtualServerKeys(c.virtualServers) { 1286 vs := c.virtualServers[key] 1287 1288 vsrs, warnings := c.buildVirtualServerRoutes(vs) 1289 resource := NewVirtualServerConfiguration(vs, vsrs, warnings) 1290 1291 newResources[resource.GetKeyWithKind()] = resource 1292 1293 holder, exists := newHosts[vs.Spec.Host] 1294 if !exists { 1295 newHosts[vs.Spec.Host] = resource 1296 continue 1297 } 1298 1299 warning := fmt.Sprintf("host %s is taken by another resource", vs.Spec.Host) 1300 1301 if !holder.Wins(resource) { 1302 newHosts[vs.Spec.Host] = resource 1303 holder.AddWarning(warning) 1304 } else { 1305 resource.AddWarning(warning) 1306 } 1307 } 1308 1309 // Step - 3 - Build hosts from TransportServer resources if TLS Passthrough is enabled 1310 1311 if c.isTLSPassthroughEnabled { 1312 for _, key := range getSortedTransportServerKeys(c.transportServers) { 1313 ts := c.transportServers[key] 1314 1315 if ts.Spec.Listener.Name != conf_v1alpha1.TLSPassthroughListenerName && ts.Spec.Listener.Protocol != conf_v1alpha1.TLSPassthroughListenerProtocol { 1316 continue 1317 } 1318 1319 resource := NewTransportServerConfiguration(ts) 1320 newResources[resource.GetKeyWithKind()] = resource 1321 1322 holder, exists := newHosts[ts.Spec.Host] 1323 if !exists { 1324 newHosts[ts.Spec.Host] = resource 1325 continue 1326 } 1327 1328 warning := fmt.Sprintf("host %s is taken by another resource", ts.Spec.Host) 1329 1330 if !holder.Wins(resource) { 1331 newHosts[ts.Spec.Host] = resource 1332 holder.AddWarning(warning) 1333 } else { 1334 resource.AddWarning(warning) 1335 } 1336 } 1337 } 1338 1339 return newHosts, newResources 1340 } 1341 1342 func (c *Configuration) buildMinionConfigs(masterHost string) ([]*MinionConfiguration, map[string][]string) { 1343 var minionConfigs []*MinionConfiguration 1344 childWarnings := make(map[string][]string) 1345 paths := make(map[string]*MinionConfiguration) 1346 1347 for _, minionKey := range getSortedIngressKeys(c.ingresses) { 1348 ingress := c.ingresses[minionKey] 1349 1350 if !isMinion(ingress) { 1351 continue 1352 } 1353 1354 if masterHost != ingress.Spec.Rules[0].Host { 1355 continue 1356 } 1357 1358 minionConfig := NewMinionConfiguration(ingress) 1359 1360 for _, p := range ingress.Spec.Rules[0].HTTP.Paths { 1361 holder, exists := paths[p.Path] 1362 if !exists { 1363 paths[p.Path] = minionConfig 1364 minionConfig.ValidPaths[p.Path] = true 1365 continue 1366 } 1367 1368 warning := fmt.Sprintf("path %s is taken by another resource", p.Path) 1369 1370 if !chooseObjectMetaWinner(&holder.Ingress.ObjectMeta, &ingress.ObjectMeta) { 1371 paths[p.Path] = minionConfig 1372 minionConfig.ValidPaths[p.Path] = true 1373 1374 holder.ValidPaths[p.Path] = false 1375 key := getResourceKey(&holder.Ingress.ObjectMeta) 1376 childWarnings[key] = append(childWarnings[key], warning) 1377 } else { 1378 key := getResourceKey(&minionConfig.Ingress.ObjectMeta) 1379 childWarnings[key] = append(childWarnings[key], warning) 1380 } 1381 } 1382 1383 minionConfigs = append(minionConfigs, minionConfig) 1384 } 1385 1386 return minionConfigs, childWarnings 1387 } 1388 1389 func (c *Configuration) buildVirtualServerRoutes(vs *conf_v1.VirtualServer) ([]*conf_v1.VirtualServerRoute, []string) { 1390 var vsrs []*conf_v1.VirtualServerRoute 1391 var warnings []string 1392 1393 for _, r := range vs.Spec.Routes { 1394 if r.Route == "" { 1395 continue 1396 } 1397 1398 vsrKey := r.Route 1399 1400 // if route is defined without a namespace, use the namespace of VirtualServer. 1401 if !strings.Contains(r.Route, "/") { 1402 vsrKey = fmt.Sprintf("%s/%s", vs.Namespace, r.Route) 1403 } 1404 1405 vsr, exists := c.virtualServerRoutes[vsrKey] 1406 if !exists { 1407 warning := fmt.Sprintf("VirtualServerRoute %s doesn't exist or invalid", vsrKey) 1408 warnings = append(warnings, warning) 1409 continue 1410 } 1411 1412 err := c.virtualServerValidator.ValidateVirtualServerRouteForVirtualServer(vsr, vs.Spec.Host, r.Path) 1413 if err != nil { 1414 warning := fmt.Sprintf("VirtualServerRoute %s is invalid: %v", vsrKey, err) 1415 warnings = append(warnings, warning) 1416 continue 1417 } 1418 1419 vsrs = append(vsrs, vsr) 1420 } 1421 1422 return vsrs, warnings 1423 } 1424 1425 func getSortedIngressKeys(m map[string]*networking.Ingress) []string { 1426 var keys []string 1427 1428 for k := range m { 1429 keys = append(keys, k) 1430 } 1431 1432 sort.Strings(keys) 1433 1434 return keys 1435 } 1436 1437 func getSortedVirtualServerKeys(m map[string]*conf_v1.VirtualServer) []string { 1438 var keys []string 1439 1440 for k := range m { 1441 keys = append(keys, k) 1442 } 1443 1444 sort.Strings(keys) 1445 1446 return keys 1447 } 1448 1449 func getSortedVirtualServerRouteKeys(m map[string]*conf_v1.VirtualServerRoute) []string { 1450 var keys []string 1451 1452 for k := range m { 1453 keys = append(keys, k) 1454 } 1455 1456 sort.Strings(keys) 1457 1458 return keys 1459 } 1460 1461 func getSortedProblemKeys(m map[string]ConfigurationProblem) []string { 1462 var keys []string 1463 1464 for k := range m { 1465 keys = append(keys, k) 1466 } 1467 1468 sort.Strings(keys) 1469 1470 return keys 1471 } 1472 1473 func getSortedResourceKeys(m map[string]Resource) []string { 1474 var keys []string 1475 1476 for k := range m { 1477 keys = append(keys, k) 1478 } 1479 1480 sort.Strings(keys) 1481 1482 return keys 1483 } 1484 1485 func getSortedTransportServerKeys(m map[string]*conf_v1alpha1.TransportServer) []string { 1486 var keys []string 1487 1488 for k := range m { 1489 keys = append(keys, k) 1490 } 1491 1492 sort.Strings(keys) 1493 1494 return keys 1495 } 1496 1497 func getSortedTransportServerConfigurationKeys(m map[string]*TransportServerConfiguration) []string { 1498 var keys []string 1499 1500 for k := range m { 1501 keys = append(keys, k) 1502 } 1503 1504 sort.Strings(keys) 1505 1506 return keys 1507 } 1508 1509 func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Resource) (removedHosts []string, updatedHosts []string, addedHosts []string) { 1510 for _, h := range getSortedResourceKeys(oldHosts) { 1511 _, exists := newHosts[h] 1512 if !exists { 1513 removedHosts = append(removedHosts, h) 1514 } 1515 } 1516 1517 for _, h := range getSortedResourceKeys(newHosts) { 1518 _, exists := oldHosts[h] 1519 if !exists { 1520 addedHosts = append(addedHosts, h) 1521 } 1522 } 1523 1524 for _, h := range getSortedResourceKeys(newHosts) { 1525 oldR, exists := oldHosts[h] 1526 if !exists { 1527 continue 1528 } 1529 1530 if !oldR.IsEqual(newHosts[h]) { 1531 updatedHosts = append(updatedHosts, h) 1532 } 1533 } 1534 1535 return removedHosts, updatedHosts, addedHosts 1536 } 1537 1538 func detectChangesInListeners(oldListeners map[string]*TransportServerConfiguration, newListeners map[string]*TransportServerConfiguration) (removedListeners []string, 1539 updatedListeners []string, addedListeners []string) { 1540 for _, l := range getSortedTransportServerConfigurationKeys(oldListeners) { 1541 _, exists := newListeners[l] 1542 if !exists { 1543 removedListeners = append(removedListeners, l) 1544 } 1545 } 1546 1547 for _, l := range getSortedTransportServerConfigurationKeys(newListeners) { 1548 _, exists := oldListeners[l] 1549 if !exists { 1550 addedListeners = append(addedListeners, l) 1551 } 1552 } 1553 1554 for _, l := range getSortedTransportServerConfigurationKeys(newListeners) { 1555 oldR, exists := oldListeners[l] 1556 if !exists { 1557 continue 1558 } 1559 1560 if !oldR.IsEqual(newListeners[l]) { 1561 updatedListeners = append(updatedListeners, l) 1562 } 1563 } 1564 1565 return removedListeners, updatedListeners, addedListeners 1566 }