sigs.k8s.io/cluster-api@v1.7.1/util/util.go (about) 1 /* 2 Copyright 2017 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 util implements utilities. 18 package util 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 "math" 25 "math/rand" 26 "reflect" 27 "strings" 28 "time" 29 30 "github.com/blang/semver/v4" 31 "github.com/pkg/errors" 32 corev1 "k8s.io/api/core/v1" 33 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 34 apierrors "k8s.io/apimachinery/pkg/api/errors" 35 "k8s.io/apimachinery/pkg/api/meta" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/types" 41 k8sversion "k8s.io/apimachinery/pkg/version" 42 ctrl "sigs.k8s.io/controller-runtime" 43 "sigs.k8s.io/controller-runtime/pkg/client" 44 "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 45 "sigs.k8s.io/controller-runtime/pkg/handler" 46 "sigs.k8s.io/controller-runtime/pkg/reconcile" 47 48 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 49 "sigs.k8s.io/cluster-api/util/annotations" 50 "sigs.k8s.io/cluster-api/util/contract" 51 "sigs.k8s.io/cluster-api/util/labels/format" 52 ) 53 54 const ( 55 // CharSet defines the alphanumeric set for random string generation. 56 CharSet = "0123456789abcdefghijklmnopqrstuvwxyz" 57 ) 58 59 var ( 60 rnd = rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec 61 62 // ErrNoCluster is returned when the cluster 63 // label could not be found on the object passed in. 64 ErrNoCluster = fmt.Errorf("no %q label present", clusterv1.ClusterNameLabel) 65 66 // ErrUnstructuredFieldNotFound determines that a field 67 // in an unstructured object could not be found. 68 ErrUnstructuredFieldNotFound = fmt.Errorf("field not found") 69 ) 70 71 // RandomString returns a random alphanumeric string. 72 func RandomString(n int) string { 73 result := make([]byte, n) 74 for i := range result { 75 result[i] = CharSet[rnd.Intn(len(CharSet))] 76 } 77 return string(result) 78 } 79 80 // Ordinalize takes an int and returns the ordinalized version of it. 81 // Eg. 1 --> 1st, 103 --> 103rd. 82 func Ordinalize(n int) string { 83 m := map[int]string{ 84 0: "th", 85 1: "st", 86 2: "nd", 87 3: "rd", 88 4: "th", 89 5: "th", 90 6: "th", 91 7: "th", 92 8: "th", 93 9: "th", 94 } 95 96 an := int(math.Abs(float64(n))) 97 if an < 10 { 98 return fmt.Sprintf("%d%s", n, m[an]) 99 } 100 return fmt.Sprintf("%d%s", n, m[an%10]) 101 } 102 103 // IsExternalManagedControlPlane returns a bool indicating whether the control plane referenced 104 // in the passed Unstructured resource is an externally managed control plane such as AKS, EKS, GKE, etc. 105 func IsExternalManagedControlPlane(controlPlane *unstructured.Unstructured) bool { 106 managed, found, err := unstructured.NestedBool(controlPlane.Object, "status", "externalManagedControlPlane") 107 if err != nil || !found { 108 return false 109 } 110 return managed 111 } 112 113 // GetMachineIfExists gets a machine from the API server if it exists. 114 func GetMachineIfExists(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.Machine, error) { 115 if c == nil { 116 // Being called before k8s is setup as part of control plane VM creation 117 return nil, nil 118 } 119 120 // Machines are identified by name 121 machine := &clusterv1.Machine{} 122 err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, machine) 123 if err != nil { 124 if apierrors.IsNotFound(err) { 125 return nil, nil 126 } 127 return nil, err 128 } 129 130 return machine, nil 131 } 132 133 // IsControlPlaneMachine checks machine is a control plane node. 134 func IsControlPlaneMachine(machine *clusterv1.Machine) bool { 135 _, ok := machine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] 136 return ok 137 } 138 139 // IsNodeReady returns true if a node is ready. 140 func IsNodeReady(node *corev1.Node) bool { 141 for _, condition := range node.Status.Conditions { 142 if condition.Type == corev1.NodeReady { 143 return condition.Status == corev1.ConditionTrue 144 } 145 } 146 147 return false 148 } 149 150 // GetClusterFromMetadata returns the Cluster object (if present) using the object metadata. 151 func GetClusterFromMetadata(ctx context.Context, c client.Client, obj metav1.ObjectMeta) (*clusterv1.Cluster, error) { 152 if obj.Labels[clusterv1.ClusterNameLabel] == "" { 153 return nil, errors.WithStack(ErrNoCluster) 154 } 155 return GetClusterByName(ctx, c, obj.Namespace, obj.Labels[clusterv1.ClusterNameLabel]) 156 } 157 158 // GetOwnerCluster returns the Cluster object owning the current resource. 159 func GetOwnerCluster(ctx context.Context, c client.Client, obj metav1.ObjectMeta) (*clusterv1.Cluster, error) { 160 for _, ref := range obj.GetOwnerReferences() { 161 if ref.Kind != "Cluster" { 162 continue 163 } 164 gv, err := schema.ParseGroupVersion(ref.APIVersion) 165 if err != nil { 166 return nil, errors.WithStack(err) 167 } 168 if gv.Group == clusterv1.GroupVersion.Group { 169 return GetClusterByName(ctx, c, obj.Namespace, ref.Name) 170 } 171 } 172 return nil, nil 173 } 174 175 // GetClusterByName finds and return a Cluster object using the specified params. 176 func GetClusterByName(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.Cluster, error) { 177 cluster := &clusterv1.Cluster{} 178 key := client.ObjectKey{ 179 Namespace: namespace, 180 Name: name, 181 } 182 183 if err := c.Get(ctx, key, cluster); err != nil { 184 return nil, errors.Wrapf(err, "failed to get Cluster/%s", name) 185 } 186 187 return cluster, nil 188 } 189 190 // ObjectKey returns client.ObjectKey for the object. 191 func ObjectKey(object metav1.Object) client.ObjectKey { 192 return client.ObjectKey{ 193 Namespace: object.GetNamespace(), 194 Name: object.GetName(), 195 } 196 } 197 198 // ClusterToInfrastructureMapFunc returns a handler.ToRequestsFunc that watches for 199 // Cluster events and returns reconciliation requests for an infrastructure provider object. 200 func ClusterToInfrastructureMapFunc(ctx context.Context, gvk schema.GroupVersionKind, c client.Client, providerCluster client.Object) handler.MapFunc { 201 log := ctrl.LoggerFrom(ctx) 202 return func(ctx context.Context, o client.Object) []reconcile.Request { 203 cluster, ok := o.(*clusterv1.Cluster) 204 if !ok { 205 return nil 206 } 207 208 // Return early if the InfrastructureRef is nil. 209 if cluster.Spec.InfrastructureRef == nil { 210 return nil 211 } 212 gk := gvk.GroupKind() 213 // Return early if the GroupKind doesn't match what we expect. 214 infraGK := cluster.Spec.InfrastructureRef.GroupVersionKind().GroupKind() 215 if gk != infraGK { 216 return nil 217 } 218 providerCluster := providerCluster.DeepCopyObject().(client.Object) 219 key := types.NamespacedName{Namespace: cluster.Namespace, Name: cluster.Spec.InfrastructureRef.Name} 220 221 if err := c.Get(ctx, key, providerCluster); err != nil { 222 log.V(4).Error(err, fmt.Sprintf("Failed to get %T", providerCluster)) 223 return nil 224 } 225 226 if annotations.IsExternallyManaged(providerCluster) { 227 log.V(4).Info(fmt.Sprintf("%T is externally managed, skipping mapping", providerCluster)) 228 return nil 229 } 230 231 return []reconcile.Request{ 232 { 233 NamespacedName: client.ObjectKey{ 234 Namespace: cluster.Namespace, 235 Name: cluster.Spec.InfrastructureRef.Name, 236 }, 237 }, 238 } 239 } 240 } 241 242 // GetOwnerMachine returns the Machine object owning the current resource. 243 func GetOwnerMachine(ctx context.Context, c client.Client, obj metav1.ObjectMeta) (*clusterv1.Machine, error) { 244 for _, ref := range obj.GetOwnerReferences() { 245 gv, err := schema.ParseGroupVersion(ref.APIVersion) 246 if err != nil { 247 return nil, err 248 } 249 if ref.Kind == "Machine" && gv.Group == clusterv1.GroupVersion.Group { 250 return GetMachineByName(ctx, c, obj.Namespace, ref.Name) 251 } 252 } 253 return nil, nil 254 } 255 256 // GetMachineByName finds and return a Machine object using the specified params. 257 func GetMachineByName(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.Machine, error) { 258 m := &clusterv1.Machine{} 259 key := client.ObjectKey{Name: name, Namespace: namespace} 260 if err := c.Get(ctx, key, m); err != nil { 261 return nil, err 262 } 263 return m, nil 264 } 265 266 // MachineToInfrastructureMapFunc returns a handler.ToRequestsFunc that watches for 267 // Machine events and returns reconciliation requests for an infrastructure provider object. 268 func MachineToInfrastructureMapFunc(gvk schema.GroupVersionKind) handler.MapFunc { 269 return func(_ context.Context, o client.Object) []reconcile.Request { 270 m, ok := o.(*clusterv1.Machine) 271 if !ok { 272 return nil 273 } 274 275 gk := gvk.GroupKind() 276 // Return early if the GroupKind doesn't match what we expect. 277 infraGK := m.Spec.InfrastructureRef.GroupVersionKind().GroupKind() 278 if gk != infraGK { 279 return nil 280 } 281 282 return []reconcile.Request{ 283 { 284 NamespacedName: client.ObjectKey{ 285 Namespace: m.Namespace, 286 Name: m.Spec.InfrastructureRef.Name, 287 }, 288 }, 289 } 290 } 291 } 292 293 // HasOwnerRef returns true if the OwnerReference is already in the slice. It matches based on Group, Kind and Name. 294 func HasOwnerRef(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) bool { 295 return indexOwnerRef(ownerReferences, ref) > -1 296 } 297 298 // EnsureOwnerRef makes sure the slice contains the OwnerReference. 299 // Note: EnsureOwnerRef will update the version of the OwnerReference fi it exists with a different version. It will also update the UID. 300 func EnsureOwnerRef(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) []metav1.OwnerReference { 301 idx := indexOwnerRef(ownerReferences, ref) 302 if idx == -1 { 303 return append(ownerReferences, ref) 304 } 305 ownerReferences[idx] = ref 306 return ownerReferences 307 } 308 309 // ReplaceOwnerRef re-parents an object from one OwnerReference to another 310 // It compares strictly based on UID to avoid reparenting across an intentional deletion: if an object is deleted 311 // and re-created with the same name and namespace, the only way to tell there was an in-progress deletion 312 // is by comparing the UIDs. 313 func ReplaceOwnerRef(ownerReferences []metav1.OwnerReference, source metav1.Object, target metav1.OwnerReference) []metav1.OwnerReference { 314 fi := -1 315 for index, r := range ownerReferences { 316 if r.UID == source.GetUID() { 317 fi = index 318 ownerReferences[index] = target 319 break 320 } 321 } 322 if fi < 0 { 323 ownerReferences = append(ownerReferences, target) 324 } 325 return ownerReferences 326 } 327 328 // RemoveOwnerRef returns the slice of owner references after removing the supplied owner ref. 329 // Note: RemoveOwnerRef ignores apiVersion and UID. It will remove the passed ownerReference where it matches Name, Group and Kind. 330 func RemoveOwnerRef(ownerReferences []metav1.OwnerReference, inputRef metav1.OwnerReference) []metav1.OwnerReference { 331 if index := indexOwnerRef(ownerReferences, inputRef); index != -1 { 332 return append(ownerReferences[:index], ownerReferences[index+1:]...) 333 } 334 return ownerReferences 335 } 336 337 // indexOwnerRef returns the index of the owner reference in the slice if found, or -1. 338 func indexOwnerRef(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) int { 339 for index, r := range ownerReferences { 340 if referSameObject(r, ref) { 341 return index 342 } 343 } 344 return -1 345 } 346 347 // IsOwnedByObject returns true if any of the owner references point to the given target. 348 // It matches the object based on the Group, Kind and Name. 349 func IsOwnedByObject(obj metav1.Object, target client.Object) bool { 350 for _, ref := range obj.GetOwnerReferences() { 351 ref := ref 352 if refersTo(&ref, target) { 353 return true 354 } 355 } 356 return false 357 } 358 359 // IsControlledBy differs from metav1.IsControlledBy. This function matches on Group, Kind and Name. The metav1.IsControlledBy function matches on UID only. 360 func IsControlledBy(obj metav1.Object, owner client.Object) bool { 361 controllerRef := metav1.GetControllerOfNoCopy(obj) 362 if controllerRef == nil { 363 return false 364 } 365 return refersTo(controllerRef, owner) 366 } 367 368 // Returns true if a and b point to the same object based on Group, Kind and Name. 369 func referSameObject(a, b metav1.OwnerReference) bool { 370 aGV, err := schema.ParseGroupVersion(a.APIVersion) 371 if err != nil { 372 return false 373 } 374 375 bGV, err := schema.ParseGroupVersion(b.APIVersion) 376 if err != nil { 377 return false 378 } 379 380 return aGV.Group == bGV.Group && a.Kind == b.Kind && a.Name == b.Name 381 } 382 383 // Returns true if ref refers to obj based on Group, Kind and Name. 384 func refersTo(ref *metav1.OwnerReference, obj client.Object) bool { 385 refGv, err := schema.ParseGroupVersion(ref.APIVersion) 386 if err != nil { 387 return false 388 } 389 390 gvk := obj.GetObjectKind().GroupVersionKind() 391 return refGv.Group == gvk.Group && ref.Kind == gvk.Kind && ref.Name == obj.GetName() 392 } 393 394 // UnstructuredUnmarshalField is a wrapper around json and unstructured objects to decode and copy a specific field 395 // value into an object. 396 func UnstructuredUnmarshalField(obj *unstructured.Unstructured, v interface{}, fields ...string) error { 397 if obj == nil || obj.Object == nil { 398 return errors.Errorf("failed to unmarshal unstructured object: object is nil") 399 } 400 401 value, found, err := unstructured.NestedFieldNoCopy(obj.Object, fields...) 402 if err != nil { 403 return errors.Wrapf(err, "failed to retrieve field %q from %q", strings.Join(fields, "."), obj.GroupVersionKind()) 404 } 405 if !found || value == nil { 406 return ErrUnstructuredFieldNotFound 407 } 408 valueBytes, err := json.Marshal(value) 409 if err != nil { 410 return errors.Wrapf(err, "failed to json-encode field %q value from %q", strings.Join(fields, "."), obj.GroupVersionKind()) 411 } 412 if err := json.Unmarshal(valueBytes, v); err != nil { 413 return errors.Wrapf(err, "failed to json-decode field %q value from %q", strings.Join(fields, "."), obj.GroupVersionKind()) 414 } 415 return nil 416 } 417 418 // HasOwner checks if any of the references in the passed list match the given group from apiVersion and one of the given kinds. 419 func HasOwner(refList []metav1.OwnerReference, apiVersion string, kinds []string) bool { 420 gv, err := schema.ParseGroupVersion(apiVersion) 421 if err != nil { 422 return false 423 } 424 425 kindMap := make(map[string]bool) 426 for _, kind := range kinds { 427 kindMap[kind] = true 428 } 429 430 for _, mr := range refList { 431 mrGroupVersion, err := schema.ParseGroupVersion(mr.APIVersion) 432 if err != nil { 433 return false 434 } 435 436 if mrGroupVersion.Group == gv.Group && kindMap[mr.Kind] { 437 return true 438 } 439 } 440 441 return false 442 } 443 444 // GetGVKMetadata retrieves a CustomResourceDefinition metadata from the API server using partial object metadata. 445 // 446 // This function is greatly more efficient than GetCRDWithContract and should be preferred in most cases. 447 func GetGVKMetadata(ctx context.Context, c client.Client, gvk schema.GroupVersionKind) (*metav1.PartialObjectMetadata, error) { 448 meta := &metav1.PartialObjectMetadata{} 449 meta.SetName(contract.CalculateCRDName(gvk.Group, gvk.Kind)) 450 meta.SetGroupVersionKind(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")) 451 if err := c.Get(ctx, client.ObjectKeyFromObject(meta), meta); err != nil { 452 return meta, errors.Wrap(err, "failed to retrieve metadata from GVK resource") 453 } 454 return meta, nil 455 } 456 457 // KubeAwareAPIVersions is a sortable slice of kube-like version strings. 458 // 459 // Kube-like version strings are starting with a v, followed by a major version, 460 // optional "alpha" or "beta" strings followed by a minor version (e.g. v1, v2beta1). 461 // Versions will be sorted based on GA/alpha/beta first and then major and minor 462 // versions. e.g. v2, v1, v1beta2, v1beta1, v1alpha1. 463 type KubeAwareAPIVersions []string 464 465 func (k KubeAwareAPIVersions) Len() int { return len(k) } 466 func (k KubeAwareAPIVersions) Swap(i, j int) { k[i], k[j] = k[j], k[i] } 467 func (k KubeAwareAPIVersions) Less(i, j int) bool { 468 return k8sversion.CompareKubeAwareVersionStrings(k[i], k[j]) < 0 469 } 470 471 // ClusterToTypedObjectsMapper returns a mapper function that gets a cluster and lists all objects for the object passed in 472 // and returns a list of requests. 473 // Note: This function uses the passed in typed ObjectList and thus with the default client configuration all list calls 474 // will be cached. 475 // NB: The objects are required to have `clusterv1.ClusterNameLabel` applied. 476 func ClusterToTypedObjectsMapper(c client.Client, ro client.ObjectList, scheme *runtime.Scheme) (handler.MapFunc, error) { 477 gvk, err := apiutil.GVKForObject(ro, scheme) 478 if err != nil { 479 return nil, err 480 } 481 482 // Note: we create the typed ObjectList once here, so we don't have to use 483 // reflection in every execution of the actual event handler. 484 obj, err := scheme.New(gvk) 485 if err != nil { 486 return nil, errors.Wrapf(err, "failed to construct object of type %s", gvk) 487 } 488 objectList, ok := obj.(client.ObjectList) 489 if !ok { 490 return nil, errors.Errorf("expected object to be a client.ObjectList, is actually %T", obj) 491 } 492 493 isNamespaced, err := isAPINamespaced(gvk, c.RESTMapper()) 494 if err != nil { 495 return nil, err 496 } 497 498 return func(ctx context.Context, o client.Object) []ctrl.Request { 499 cluster, ok := o.(*clusterv1.Cluster) 500 if !ok { 501 return nil 502 } 503 504 listOpts := []client.ListOption{ 505 client.MatchingLabels{ 506 clusterv1.ClusterNameLabel: cluster.Name, 507 }, 508 } 509 510 if isNamespaced { 511 listOpts = append(listOpts, client.InNamespace(cluster.Namespace)) 512 } 513 514 objectList = objectList.DeepCopyObject().(client.ObjectList) 515 if err := c.List(ctx, objectList, listOpts...); err != nil { 516 return nil 517 } 518 519 objects, err := meta.ExtractList(objectList) 520 if err != nil { 521 return nil 522 } 523 524 results := []ctrl.Request{} 525 for _, obj := range objects { 526 // Note: We don't check if the type cast succeeds as all items in an client.ObjectList 527 // are client.Objects. 528 o := obj.(client.Object) 529 results = append(results, ctrl.Request{ 530 NamespacedName: client.ObjectKey{Namespace: o.GetNamespace(), Name: o.GetName()}, 531 }) 532 } 533 return results 534 }, nil 535 } 536 537 // MachineDeploymentToObjectsMapper returns a mapper function that gets a machinedeployment 538 // and lists all objects for the object passed in and returns a list of requests. 539 // NB: The objects are required to have `clusterv1.MachineDeploymentNameLabel` applied. 540 func MachineDeploymentToObjectsMapper(c client.Client, ro client.ObjectList, scheme *runtime.Scheme) (handler.MapFunc, error) { 541 gvk, err := apiutil.GVKForObject(ro, scheme) 542 if err != nil { 543 return nil, err 544 } 545 546 // Note: we create the typed ObjectList once here, so we don't have to use 547 // reflection in every execution of the actual event handler. 548 obj, err := scheme.New(gvk) 549 if err != nil { 550 return nil, errors.Wrapf(err, "failed to construct object of type %s", gvk) 551 } 552 objectList, ok := obj.(client.ObjectList) 553 if !ok { 554 return nil, errors.Errorf("expected object to be a client.ObjectList, is actually %T", obj) 555 } 556 557 isNamespaced, err := isAPINamespaced(gvk, c.RESTMapper()) 558 if err != nil { 559 return nil, err 560 } 561 562 return func(ctx context.Context, o client.Object) []ctrl.Request { 563 md, ok := o.(*clusterv1.MachineDeployment) 564 if !ok { 565 return nil 566 } 567 568 listOpts := []client.ListOption{ 569 client.MatchingLabels{ 570 clusterv1.MachineDeploymentNameLabel: md.Name, 571 }, 572 } 573 574 if isNamespaced { 575 listOpts = append(listOpts, client.InNamespace(md.Namespace)) 576 } 577 578 objectList = objectList.DeepCopyObject().(client.ObjectList) 579 if err := c.List(ctx, objectList, listOpts...); err != nil { 580 return nil 581 } 582 583 objects, err := meta.ExtractList(objectList) 584 if err != nil { 585 return nil 586 } 587 588 results := []ctrl.Request{} 589 for _, obj := range objects { 590 // Note: We don't check if the type cast succeeds as all items in an client.ObjectList 591 // are client.Objects. 592 o := obj.(client.Object) 593 results = append(results, ctrl.Request{ 594 NamespacedName: client.ObjectKey{Namespace: o.GetNamespace(), Name: o.GetName()}, 595 }) 596 } 597 return results 598 }, nil 599 } 600 601 // MachineSetToObjectsMapper returns a mapper function that gets a machineset 602 // and lists all objects for the object passed in and returns a list of requests. 603 // NB: The objects are required to have `clusterv1.MachineSetNameLabel` applied. 604 func MachineSetToObjectsMapper(c client.Client, ro client.ObjectList, scheme *runtime.Scheme) (handler.MapFunc, error) { 605 gvk, err := apiutil.GVKForObject(ro, scheme) 606 if err != nil { 607 return nil, err 608 } 609 610 // Note: we create the typed ObjectList once here, so we don't have to use 611 // reflection in every execution of the actual event handler. 612 obj, err := scheme.New(gvk) 613 if err != nil { 614 return nil, errors.Wrapf(err, "failed to construct object of type %s", gvk) 615 } 616 objectList, ok := obj.(client.ObjectList) 617 if !ok { 618 return nil, errors.Errorf("expected object to be a client.ObjectList, is actually %T", obj) 619 } 620 621 isNamespaced, err := isAPINamespaced(gvk, c.RESTMapper()) 622 if err != nil { 623 return nil, err 624 } 625 626 return func(ctx context.Context, o client.Object) []ctrl.Request { 627 ms, ok := o.(*clusterv1.MachineSet) 628 if !ok { 629 return nil 630 } 631 632 listOpts := []client.ListOption{ 633 client.MatchingLabels{ 634 clusterv1.MachineSetNameLabel: format.MustFormatValue(ms.Name), 635 }, 636 } 637 638 if isNamespaced { 639 listOpts = append(listOpts, client.InNamespace(ms.Namespace)) 640 } 641 642 objectList = objectList.DeepCopyObject().(client.ObjectList) 643 if err := c.List(ctx, objectList, listOpts...); err != nil { 644 return nil 645 } 646 647 objects, err := meta.ExtractList(objectList) 648 if err != nil { 649 return nil 650 } 651 652 results := []ctrl.Request{} 653 for _, obj := range objects { 654 // Note: We don't check if the type cast succeeds as all items in an client.ObjectList 655 // are client.Objects. 656 o := obj.(client.Object) 657 results = append(results, ctrl.Request{ 658 NamespacedName: client.ObjectKey{Namespace: o.GetNamespace(), Name: o.GetName()}, 659 }) 660 } 661 return results 662 }, nil 663 } 664 665 // isAPINamespaced detects if a GroupVersionKind is namespaced. 666 func isAPINamespaced(gk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) { 667 restMapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind}) 668 if err != nil { 669 return false, fmt.Errorf("failed to get restmapping: %w", err) 670 } 671 672 switch restMapping.Scope.Name() { 673 case "": 674 return false, errors.New("Scope cannot be identified. Empty scope returned") 675 case meta.RESTScopeNameRoot: 676 return false, nil 677 default: 678 return true, nil 679 } 680 } 681 682 // ObjectReferenceToUnstructured converts an object reference to an unstructured object. 683 func ObjectReferenceToUnstructured(in corev1.ObjectReference) *unstructured.Unstructured { 684 out := &unstructured.Unstructured{} 685 out.SetKind(in.Kind) 686 out.SetAPIVersion(in.APIVersion) 687 out.SetNamespace(in.Namespace) 688 out.SetName(in.Name) 689 return out 690 } 691 692 // IsSupportedVersionSkew will return true if a and b are no more than one minor version off from each other. 693 func IsSupportedVersionSkew(a, b semver.Version) bool { 694 if a.Major != b.Major { 695 return false 696 } 697 if a.Minor > b.Minor { 698 return a.Minor-b.Minor == 1 699 } 700 return b.Minor-a.Minor <= 1 701 } 702 703 // LowestNonZeroResult compares two reconciliation results 704 // and returns the one with lowest requeue time. 705 func LowestNonZeroResult(i, j ctrl.Result) ctrl.Result { 706 switch { 707 case i.IsZero(): 708 return j 709 case j.IsZero(): 710 return i 711 case i.Requeue: 712 return i 713 case j.Requeue: 714 return j 715 case i.RequeueAfter < j.RequeueAfter: 716 return i 717 default: 718 return j 719 } 720 } 721 722 // LowestNonZeroInt32 returns the lowest non-zero value of the two provided values. 723 func LowestNonZeroInt32(i, j int32) int32 { 724 if i == 0 { 725 return j 726 } 727 if j == 0 { 728 return i 729 } 730 if i < j { 731 return i 732 } 733 return j 734 } 735 736 // IsNil returns an error if the passed interface is equal to nil or if it has an interface value of nil. 737 func IsNil(i interface{}) bool { 738 if i == nil { 739 return true 740 } 741 switch reflect.TypeOf(i).Kind() { 742 case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Slice, reflect.Interface, reflect.UnsafePointer, reflect.Func: 743 return reflect.ValueOf(i).IsValid() && reflect.ValueOf(i).IsNil() 744 } 745 return false 746 } 747 748 // MergeMap merges maps. 749 // NOTE: In case a key exists in multiple maps, the value of the first map is preserved. 750 func MergeMap(maps ...map[string]string) map[string]string { 751 m := make(map[string]string) 752 for i := len(maps) - 1; i >= 0; i-- { 753 for k, v := range maps[i] { 754 m[k] = v 755 } 756 } 757 758 // Nil the result if the map is empty, thus avoiding triggering infinite reconcile 759 // given that at json level label: {} or annotation: {} is different from no field, which is the 760 // corresponding value stored in etcd given that those fields are defined as omitempty. 761 if len(m) == 0 { 762 return nil 763 } 764 return m 765 }