sigs.k8s.io/cluster-api@v1.7.1/util/predicates/generic_predicates.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package predicates
    18  
    19  import (
    20  	"strings"
    21  
    22  	"github.com/go-logr/logr"
    23  	"sigs.k8s.io/controller-runtime/pkg/client"
    24  	"sigs.k8s.io/controller-runtime/pkg/event"
    25  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    26  
    27  	"sigs.k8s.io/cluster-api/util/annotations"
    28  	"sigs.k8s.io/cluster-api/util/labels"
    29  )
    30  
    31  // All returns a predicate that returns true only if all given predicates return true.
    32  func All(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
    33  	return predicate.Funcs{
    34  		UpdateFunc: func(e event.UpdateEvent) bool {
    35  			log := logger.WithValues("predicateAggregation", "All")
    36  			for _, p := range predicates {
    37  				if !p.UpdateFunc(e) {
    38  					log.V(6).Info("One of the provided predicates returned false, blocking further processing")
    39  					return false
    40  				}
    41  			}
    42  			log.V(6).Info("All provided predicates returned true, allowing further processing")
    43  			return true
    44  		},
    45  		CreateFunc: func(e event.CreateEvent) bool {
    46  			log := logger.WithValues("predicateAggregation", "All")
    47  			for _, p := range predicates {
    48  				if !p.CreateFunc(e) {
    49  					log.V(6).Info("One of the provided predicates returned false, blocking further processing")
    50  					return false
    51  				}
    52  			}
    53  			log.V(6).Info("All provided predicates returned true, allowing further processing")
    54  			return true
    55  		},
    56  		DeleteFunc: func(e event.DeleteEvent) bool {
    57  			log := logger.WithValues("predicateAggregation", "All")
    58  			for _, p := range predicates {
    59  				if !p.DeleteFunc(e) {
    60  					log.V(6).Info("One of the provided predicates returned false, blocking further processing")
    61  					return false
    62  				}
    63  			}
    64  			log.V(6).Info("All provided predicates returned true, allowing further processing")
    65  			return true
    66  		},
    67  		GenericFunc: func(e event.GenericEvent) bool {
    68  			log := logger.WithValues("predicateAggregation", "All")
    69  			for _, p := range predicates {
    70  				if !p.GenericFunc(e) {
    71  					log.V(6).Info("One of the provided predicates returned false, blocking further processing")
    72  					return false
    73  				}
    74  			}
    75  			log.V(6).Info("All provided predicates returned true, allowing further processing")
    76  			return true
    77  		},
    78  	}
    79  }
    80  
    81  // Any returns a predicate that returns true only if any given predicate returns true.
    82  func Any(logger logr.Logger, predicates ...predicate.Funcs) predicate.Funcs {
    83  	return predicate.Funcs{
    84  		UpdateFunc: func(e event.UpdateEvent) bool {
    85  			log := logger.WithValues("predicateAggregation", "Any")
    86  			for _, p := range predicates {
    87  				if p.UpdateFunc(e) {
    88  					log.V(6).Info("One of the provided predicates returned true, allowing further processing")
    89  					return true
    90  				}
    91  			}
    92  			log.V(6).Info("All of the provided predicates returned false, blocking further processing")
    93  			return false
    94  		},
    95  		CreateFunc: func(e event.CreateEvent) bool {
    96  			log := logger.WithValues("predicateAggregation", "Any")
    97  			for _, p := range predicates {
    98  				if p.CreateFunc(e) {
    99  					log.V(6).Info("One of the provided predicates returned true, allowing further processing")
   100  					return true
   101  				}
   102  			}
   103  			log.V(6).Info("All of the provided predicates returned false, blocking further processing")
   104  			return false
   105  		},
   106  		DeleteFunc: func(e event.DeleteEvent) bool {
   107  			log := logger.WithValues("predicateAggregation", "Any")
   108  			for _, p := range predicates {
   109  				if p.DeleteFunc(e) {
   110  					log.V(6).Info("One of the provided predicates returned true, allowing further processing")
   111  					return true
   112  				}
   113  			}
   114  			log.V(6).Info("All of the provided predicates returned false, blocking further processing")
   115  			return false
   116  		},
   117  		GenericFunc: func(e event.GenericEvent) bool {
   118  			log := logger.WithValues("predicateAggregation", "Any")
   119  			for _, p := range predicates {
   120  				if p.GenericFunc(e) {
   121  					log.V(6).Info("One of the provided predicates returned true, allowing further processing")
   122  					return true
   123  				}
   124  			}
   125  			log.V(6).Info("All of the provided predicates returned false, blocking further processing")
   126  			return false
   127  		},
   128  	}
   129  }
   130  
   131  // ResourceHasFilterLabel returns a predicate that returns true only if the provided resource contains
   132  // a label with the WatchLabel key and the configured label value exactly.
   133  func ResourceHasFilterLabel(logger logr.Logger, labelValue string) predicate.Funcs {
   134  	return predicate.Funcs{
   135  		UpdateFunc: func(e event.UpdateEvent) bool {
   136  			return processIfLabelMatch(logger.WithValues("predicate", "ResourceHasFilterLabel", "eventType", "update"), e.ObjectNew, labelValue)
   137  		},
   138  		CreateFunc: func(e event.CreateEvent) bool {
   139  			return processIfLabelMatch(logger.WithValues("predicate", "ResourceHasFilterLabel", "eventType", "create"), e.Object, labelValue)
   140  		},
   141  		DeleteFunc: func(e event.DeleteEvent) bool {
   142  			return processIfLabelMatch(logger.WithValues("predicate", "ResourceHasFilterLabel", "eventType", "delete"), e.Object, labelValue)
   143  		},
   144  		GenericFunc: func(e event.GenericEvent) bool {
   145  			return processIfLabelMatch(logger.WithValues("predicate", "ResourceHasFilterLabel", "eventType", "generic"), e.Object, labelValue)
   146  		},
   147  	}
   148  }
   149  
   150  // ResourceNotPaused returns a Predicate that returns true only if the provided resource does not contain the
   151  // paused annotation.
   152  // This implements a common requirement for all cluster-api and provider controllers skip reconciliation when the paused
   153  // annotation is present for a resource.
   154  // Example use:
   155  //
   156  //	func (r *MyReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
   157  //		controller, err := ctrl.NewControllerManagedBy(mgr).
   158  //			For(&v1.MyType{}).
   159  //			WithOptions(options).
   160  //			WithEventFilter(util.ResourceNotPaused(r.Log)).
   161  //			Build(r)
   162  //		return err
   163  //	}
   164  func ResourceNotPaused(logger logr.Logger) predicate.Funcs {
   165  	return predicate.Funcs{
   166  		UpdateFunc: func(e event.UpdateEvent) bool {
   167  			return processIfNotPaused(logger.WithValues("predicate", "ResourceNotPaused", "eventType", "update"), e.ObjectNew)
   168  		},
   169  		CreateFunc: func(e event.CreateEvent) bool {
   170  			return processIfNotPaused(logger.WithValues("predicate", "ResourceNotPaused", "eventType", "create"), e.Object)
   171  		},
   172  		DeleteFunc: func(e event.DeleteEvent) bool {
   173  			return processIfNotPaused(logger.WithValues("predicate", "ResourceNotPaused", "eventType", "delete"), e.Object)
   174  		},
   175  		GenericFunc: func(e event.GenericEvent) bool {
   176  			return processIfNotPaused(logger.WithValues("predicate", "ResourceNotPaused", "eventType", "generic"), e.Object)
   177  		},
   178  	}
   179  }
   180  
   181  // ResourceNotPausedAndHasFilterLabel returns a predicate that returns true only if the
   182  // ResourceNotPaused and ResourceHasFilterLabel predicates return true.
   183  func ResourceNotPausedAndHasFilterLabel(logger logr.Logger, labelValue string) predicate.Funcs {
   184  	return All(logger, ResourceNotPaused(logger), ResourceHasFilterLabel(logger, labelValue))
   185  }
   186  
   187  func processIfNotPaused(logger logr.Logger, obj client.Object) bool {
   188  	kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
   189  	log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName())
   190  	if annotations.HasPaused(obj) {
   191  		log.V(4).Info("Resource is paused, will not attempt to map resource")
   192  		return false
   193  	}
   194  	log.V(6).Info("Resource is not paused, will attempt to map resource")
   195  	return true
   196  }
   197  
   198  func processIfLabelMatch(logger logr.Logger, obj client.Object, labelValue string) bool {
   199  	// Return early if no labelValue was set.
   200  	if labelValue == "" {
   201  		return true
   202  	}
   203  
   204  	kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
   205  	log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName())
   206  	if labels.HasWatchLabel(obj, labelValue) {
   207  		log.V(6).Info("Resource matches label, will attempt to map resource")
   208  		return true
   209  	}
   210  	log.V(4).Info("Resource does not match label, will not attempt to map resource")
   211  	return false
   212  }
   213  
   214  // ResourceIsNotExternallyManaged returns a predicate that returns true only if the resource does not contain
   215  // the externally managed annotation.
   216  // This implements a requirement for InfraCluster providers to be able to ignore externally managed
   217  // cluster infrastructure.
   218  func ResourceIsNotExternallyManaged(logger logr.Logger) predicate.Funcs {
   219  	return predicate.Funcs{
   220  		UpdateFunc: func(e event.UpdateEvent) bool {
   221  			return processIfNotExternallyManaged(logger.WithValues("predicate", "ResourceIsNotExternallyManaged", "eventType", "update"), e.ObjectNew)
   222  		},
   223  		CreateFunc: func(e event.CreateEvent) bool {
   224  			return processIfNotExternallyManaged(logger.WithValues("predicate", "ResourceIsNotExternallyManaged", "eventType", "create"), e.Object)
   225  		},
   226  		DeleteFunc: func(e event.DeleteEvent) bool {
   227  			return processIfNotExternallyManaged(logger.WithValues("predicate", "ResourceIsNotExternallyManaged", "eventType", "delete"), e.Object)
   228  		},
   229  		GenericFunc: func(e event.GenericEvent) bool {
   230  			return processIfNotExternallyManaged(logger.WithValues("predicate", "ResourceIsNotExternallyManaged", "eventType", "generic"), e.Object)
   231  		},
   232  	}
   233  }
   234  
   235  func processIfNotExternallyManaged(logger logr.Logger, obj client.Object) bool {
   236  	kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
   237  	log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName())
   238  	if annotations.IsExternallyManaged(obj) {
   239  		log.V(4).Info("Resource is externally managed, will not attempt to map resource")
   240  		return false
   241  	}
   242  	log.V(6).Info("Resource is managed, will attempt to map resource")
   243  	return true
   244  }
   245  
   246  // ResourceIsTopologyOwned returns a predicate that returns true only if the resource has
   247  // the `topology.cluster.x-k8s.io/owned` label.
   248  func ResourceIsTopologyOwned(logger logr.Logger) predicate.Funcs {
   249  	return predicate.Funcs{
   250  		UpdateFunc: func(e event.UpdateEvent) bool {
   251  			return processIfTopologyOwned(logger.WithValues("predicate", "ResourceIsTopologyOwned", "eventType", "update"), e.ObjectNew)
   252  		},
   253  		CreateFunc: func(e event.CreateEvent) bool {
   254  			return processIfTopologyOwned(logger.WithValues("predicate", "ResourceIsTopologyOwned", "eventType", "create"), e.Object)
   255  		},
   256  		DeleteFunc: func(e event.DeleteEvent) bool {
   257  			return processIfTopologyOwned(logger.WithValues("predicate", "ResourceIsTopologyOwned", "eventType", "delete"), e.Object)
   258  		},
   259  		GenericFunc: func(e event.GenericEvent) bool {
   260  			return processIfTopologyOwned(logger.WithValues("predicate", "ResourceIsTopologyOwned", "eventType", "generic"), e.Object)
   261  		},
   262  	}
   263  }
   264  
   265  func processIfTopologyOwned(logger logr.Logger, obj client.Object) bool {
   266  	kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
   267  	log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName())
   268  	if labels.IsTopologyOwned(obj) {
   269  		log.V(6).Info("Resource is topology owned, will attempt to map resource")
   270  		return true
   271  	}
   272  	// We intentionally log this line only on level 6, because it will be very frequently
   273  	// logged for MachineDeployments and MachineSets not owned by a topology.
   274  	log.V(6).Info("Resource is not topology owned, will not attempt to map resource")
   275  	return false
   276  }