sigs.k8s.io/cluster-api@v1.7.1/util/predicates/cluster_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 implements predicate utilities.
    18  package predicates
    19  
    20  import (
    21  	"fmt"
    22  
    23  	"github.com/go-logr/logr"
    24  	"k8s.io/klog/v2"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  	"sigs.k8s.io/controller-runtime/pkg/event"
    27  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	"sigs.k8s.io/cluster-api/util/conditions"
    31  )
    32  
    33  // ClusterCreateInfraReady returns a predicate that returns true for a create event when a cluster has Status.InfrastructureReady set as true
    34  // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy.
    35  func ClusterCreateInfraReady(logger logr.Logger) predicate.Funcs {
    36  	return predicate.Funcs{
    37  		CreateFunc: func(e event.CreateEvent) bool {
    38  			log := logger.WithValues("predicate", "ClusterCreateInfraReady", "eventType", "create")
    39  
    40  			c, ok := e.Object.(*clusterv1.Cluster)
    41  			if !ok {
    42  				log.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", e.Object))
    43  				return false
    44  			}
    45  			log = log.WithValues("Cluster", klog.KObj(c))
    46  
    47  			// Only need to trigger a reconcile if the Cluster.Status.InfrastructureReady is true
    48  			if c.Status.InfrastructureReady {
    49  				log.V(6).Info("Cluster infrastructure is ready, allowing further processing")
    50  				return true
    51  			}
    52  
    53  			log.V(4).Info("Cluster infrastructure is not ready, blocking further processing")
    54  			return false
    55  		},
    56  		UpdateFunc:  func(event.UpdateEvent) bool { return false },
    57  		DeleteFunc:  func(event.DeleteEvent) bool { return false },
    58  		GenericFunc: func(event.GenericEvent) bool { return false },
    59  	}
    60  }
    61  
    62  // ClusterCreateNotPaused returns a predicate that returns true for a create event when a cluster has Spec.Paused set as false
    63  // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy.
    64  func ClusterCreateNotPaused(logger logr.Logger) predicate.Funcs {
    65  	return predicate.Funcs{
    66  		CreateFunc: func(e event.CreateEvent) bool {
    67  			log := logger.WithValues("predicate", "ClusterCreateNotPaused", "eventType", "create")
    68  
    69  			c, ok := e.Object.(*clusterv1.Cluster)
    70  			if !ok {
    71  				log.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", e.Object))
    72  				return false
    73  			}
    74  			log = log.WithValues("Cluster", klog.KObj(c))
    75  
    76  			// Only need to trigger a reconcile if the Cluster.Spec.Paused is false
    77  			if !c.Spec.Paused {
    78  				log.V(6).Info("Cluster is not paused, allowing further processing")
    79  				return true
    80  			}
    81  
    82  			log.V(4).Info("Cluster is paused, blocking further processing")
    83  			return false
    84  		},
    85  		UpdateFunc:  func(event.UpdateEvent) bool { return false },
    86  		DeleteFunc:  func(event.DeleteEvent) bool { return false },
    87  		GenericFunc: func(event.GenericEvent) bool { return false },
    88  	}
    89  }
    90  
    91  // ClusterUpdateInfraReady returns a predicate that returns true for an update event when a cluster has Status.InfrastructureReady changed from false to true
    92  // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy.
    93  func ClusterUpdateInfraReady(logger logr.Logger) predicate.Funcs {
    94  	return predicate.Funcs{
    95  		UpdateFunc: func(e event.UpdateEvent) bool {
    96  			log := logger.WithValues("predicate", "ClusterUpdateInfraReady", "eventType", "update")
    97  
    98  			oldCluster, ok := e.ObjectOld.(*clusterv1.Cluster)
    99  			if !ok {
   100  				log.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", e.ObjectOld))
   101  				return false
   102  			}
   103  			log = log.WithValues("Cluster", klog.KObj(oldCluster))
   104  
   105  			newCluster := e.ObjectNew.(*clusterv1.Cluster)
   106  
   107  			if !oldCluster.Status.InfrastructureReady && newCluster.Status.InfrastructureReady {
   108  				log.V(6).Info("Cluster infrastructure became ready, allowing further processing")
   109  				return true
   110  			}
   111  
   112  			log.V(4).Info("Cluster infrastructure did not become ready, blocking further processing")
   113  			return false
   114  		},
   115  		CreateFunc:  func(event.CreateEvent) bool { return false },
   116  		DeleteFunc:  func(event.DeleteEvent) bool { return false },
   117  		GenericFunc: func(event.GenericEvent) bool { return false },
   118  	}
   119  }
   120  
   121  // ClusterUpdateUnpaused returns a predicate that returns true for an update event when a cluster has Spec.Paused changed from true to false
   122  // it also returns true if the resource provided is not a Cluster to allow for use with controller-runtime NewControllerManagedBy.
   123  func ClusterUpdateUnpaused(logger logr.Logger) predicate.Funcs {
   124  	return predicate.Funcs{
   125  		UpdateFunc: func(e event.UpdateEvent) bool {
   126  			log := logger.WithValues("predicate", "ClusterUpdateUnpaused", "eventType", "update")
   127  
   128  			oldCluster, ok := e.ObjectOld.(*clusterv1.Cluster)
   129  			if !ok {
   130  				log.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", e.ObjectOld))
   131  				return false
   132  			}
   133  			log = log.WithValues("Cluster", klog.KObj(oldCluster))
   134  
   135  			newCluster := e.ObjectNew.(*clusterv1.Cluster)
   136  
   137  			if oldCluster.Spec.Paused && !newCluster.Spec.Paused {
   138  				log.V(4).Info("Cluster was unpaused, allowing further processing")
   139  				return true
   140  			}
   141  
   142  			// This predicate always work in "or" with Paused predicates
   143  			// so the logs are adjusted to not provide false negatives/verbosity al V<=5.
   144  			log.V(6).Info("Cluster was not unpaused, blocking further processing")
   145  			return false
   146  		},
   147  		CreateFunc:  func(event.CreateEvent) bool { return false },
   148  		DeleteFunc:  func(event.DeleteEvent) bool { return false },
   149  		GenericFunc: func(event.GenericEvent) bool { return false },
   150  	}
   151  }
   152  
   153  // ClusterUnpaused returns a Predicate that returns true on Cluster creation events where Cluster.Spec.Paused is false
   154  // and Update events when Cluster.Spec.Paused transitions to false.
   155  // This implements a common requirement for many cluster-api and provider controllers (such as Cluster Infrastructure
   156  // controllers) to resume reconciliation when the Cluster is unpaused.
   157  // Example use:
   158  //
   159  //	err := controller.Watch(
   160  //	    source.Kind(cache, &clusterv1.Cluster{}),
   161  //	    handler.EnqueueRequestsFromMapFunc(clusterToMachines)
   162  //	    predicates.ClusterUnpaused(r.Log),
   163  //	)
   164  func ClusterUnpaused(logger logr.Logger) predicate.Funcs {
   165  	log := logger.WithValues("predicate", "ClusterUnpaused")
   166  
   167  	// Use any to ensure we process either create or update events we care about
   168  	return Any(log, ClusterCreateNotPaused(log), ClusterUpdateUnpaused(log))
   169  }
   170  
   171  // ClusterControlPlaneInitialized returns a Predicate that returns true on Update events
   172  // when ControlPlaneInitializedCondition on a Cluster changes to true.
   173  // Example use:
   174  //
   175  //	err := controller.Watch(
   176  //	    source.Kind(cache, &clusterv1.Cluster{}),
   177  //	    handler.EnqueueRequestsFromMapFunc(clusterToMachines)
   178  //	    predicates.ClusterControlPlaneInitialized(r.Log),
   179  //	)
   180  func ClusterControlPlaneInitialized(logger logr.Logger) predicate.Funcs {
   181  	return predicate.Funcs{
   182  		UpdateFunc: func(e event.UpdateEvent) bool {
   183  			log := logger.WithValues("predicate", "ClusterControlPlaneInitialized", "eventType", "update")
   184  
   185  			oldCluster, ok := e.ObjectOld.(*clusterv1.Cluster)
   186  			if !ok {
   187  				log.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", e.ObjectOld))
   188  				return false
   189  			}
   190  			log = log.WithValues("Cluster", klog.KObj(oldCluster))
   191  
   192  			newCluster := e.ObjectNew.(*clusterv1.Cluster)
   193  
   194  			if !conditions.IsTrue(oldCluster, clusterv1.ControlPlaneInitializedCondition) &&
   195  				conditions.IsTrue(newCluster, clusterv1.ControlPlaneInitializedCondition) {
   196  				log.V(6).Info("Cluster ControlPlaneInitialized was set, allow further processing")
   197  				return true
   198  			}
   199  
   200  			log.V(6).Info("Cluster ControlPlaneInitialized hasn't changed, blocking further processing")
   201  			return false
   202  		},
   203  		CreateFunc:  func(event.CreateEvent) bool { return false },
   204  		DeleteFunc:  func(event.DeleteEvent) bool { return false },
   205  		GenericFunc: func(event.GenericEvent) bool { return false },
   206  	}
   207  }
   208  
   209  // ClusterUnpausedAndInfrastructureReady returns a Predicate that returns true on Cluster creation events where
   210  // both Cluster.Spec.Paused is false and Cluster.Status.InfrastructureReady is true and Update events when
   211  // either Cluster.Spec.Paused transitions to false or Cluster.Status.InfrastructureReady transitions to true.
   212  // This implements a common requirement for some cluster-api and provider controllers (such as Machine Infrastructure
   213  // controllers) to resume reconciliation when the Cluster is unpaused and when the infrastructure becomes ready.
   214  // Example use:
   215  //
   216  //	err := controller.Watch(
   217  //	    source.Kind(cache, &clusterv1.Cluster{}),
   218  //	    handler.EnqueueRequestsFromMapFunc(clusterToMachines)
   219  //	    predicates.ClusterUnpausedAndInfrastructureReady(r.Log),
   220  //	)
   221  func ClusterUnpausedAndInfrastructureReady(logger logr.Logger) predicate.Funcs {
   222  	log := logger.WithValues("predicate", "ClusterUnpausedAndInfrastructureReady")
   223  
   224  	// Only continue processing create events if both not paused and infrastructure is ready
   225  	createPredicates := All(log, ClusterCreateNotPaused(log), ClusterCreateInfraReady(log))
   226  
   227  	// Process update events if either Cluster is unpaused or infrastructure becomes ready
   228  	updatePredicates := Any(log, ClusterUpdateUnpaused(log), ClusterUpdateInfraReady(log))
   229  
   230  	// Use any to ensure we process either create or update events we care about
   231  	return Any(log, createPredicates, updatePredicates)
   232  }
   233  
   234  // ClusterHasTopology returns a Predicate that returns true when cluster.Spec.Topology
   235  // is NOT nil and false otherwise.
   236  func ClusterHasTopology(logger logr.Logger) predicate.Funcs {
   237  	return predicate.Funcs{
   238  		UpdateFunc: func(e event.UpdateEvent) bool {
   239  			return processIfTopologyManaged(logger.WithValues("predicate", "ClusterHasTopology", "eventType", "update"), e.ObjectNew)
   240  		},
   241  		CreateFunc: func(e event.CreateEvent) bool {
   242  			return processIfTopologyManaged(logger.WithValues("predicate", "ClusterHasTopology", "eventType", "create"), e.Object)
   243  		},
   244  		DeleteFunc: func(e event.DeleteEvent) bool {
   245  			return processIfTopologyManaged(logger.WithValues("predicate", "ClusterHasTopology", "eventType", "delete"), e.Object)
   246  		},
   247  		GenericFunc: func(e event.GenericEvent) bool {
   248  			return processIfTopologyManaged(logger.WithValues("predicate", "ClusterHasTopology", "eventType", "generic"), e.Object)
   249  		},
   250  	}
   251  }
   252  
   253  func processIfTopologyManaged(logger logr.Logger, object client.Object) bool {
   254  	cluster, ok := object.(*clusterv1.Cluster)
   255  	if !ok {
   256  		logger.V(4).Info("Expected Cluster", "type", fmt.Sprintf("%T", object))
   257  		return false
   258  	}
   259  
   260  	log := logger.WithValues("Cluster", klog.KObj(cluster))
   261  
   262  	if cluster.Spec.Topology != nil {
   263  		log.V(6).Info("Cluster has topology, allowing further processing")
   264  		return true
   265  	}
   266  
   267  	log.V(6).Info("Cluster does not have topology, blocking further processing")
   268  	return false
   269  }