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 }