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 }