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  }