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  }