sigs.k8s.io/cluster-api@v1.7.1/bootstrap/util/configowner.go (about)

     1  /*
     2  Copyright 2019 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 kubeadm utility functionality.
    18  package util
    19  
    20  import (
    21  	"context"
    22  
    23  	"github.com/pkg/errors"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api/controllers/external"
    34  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api/feature"
    36  )
    37  
    38  // ConfigOwner provides a data interface for different config owner types.
    39  type ConfigOwner struct {
    40  	*unstructured.Unstructured
    41  }
    42  
    43  // IsInfrastructureReady extracts infrastructure status from the config owner.
    44  func (co ConfigOwner) IsInfrastructureReady() bool {
    45  	infrastructureReady, _, err := unstructured.NestedBool(co.Object, "status", "infrastructureReady")
    46  	if err != nil {
    47  		return false
    48  	}
    49  	return infrastructureReady
    50  }
    51  
    52  // HasNodeRefs checks if the config owner has nodeRefs. For a Machine this means
    53  // that it has a nodeRef. For a MachinePool it means that it has as many nodeRefs
    54  // as there are replicas.
    55  func (co ConfigOwner) HasNodeRefs() bool {
    56  	if co.IsMachinePool() {
    57  		numExpectedNodes, found, err := unstructured.NestedInt64(co.Object, "spec", "replicas")
    58  		if err != nil {
    59  			return false
    60  		}
    61  		// replicas default to 1 so this is what we should use if nothing is specified
    62  		if !found {
    63  			numExpectedNodes = 1
    64  		}
    65  		nodeRefs, _, err := unstructured.NestedSlice(co.Object, "status", "nodeRefs")
    66  		if err != nil {
    67  			return false
    68  		}
    69  		return len(nodeRefs) == int(numExpectedNodes)
    70  	}
    71  	nodeRef, _, err := unstructured.NestedMap(co.Object, "status", "nodeRef")
    72  	if err != nil {
    73  		return false
    74  	}
    75  	return len(nodeRef) > 0
    76  }
    77  
    78  // ClusterName extracts spec.clusterName from the config owner.
    79  func (co ConfigOwner) ClusterName() string {
    80  	clusterName, _, err := unstructured.NestedString(co.Object, "spec", "clusterName")
    81  	if err != nil {
    82  		return ""
    83  	}
    84  	return clusterName
    85  }
    86  
    87  // DataSecretName extracts spec.bootstrap.dataSecretName from the config owner.
    88  func (co ConfigOwner) DataSecretName() *string {
    89  	dataSecretName, exist, err := unstructured.NestedString(co.Object, "spec", "bootstrap", "dataSecretName")
    90  	if err != nil || !exist {
    91  		return nil
    92  	}
    93  	return &dataSecretName
    94  }
    95  
    96  // IsControlPlaneMachine checks if an unstructured object is Machine with the control plane role.
    97  func (co ConfigOwner) IsControlPlaneMachine() bool {
    98  	if co.GetKind() != "Machine" {
    99  		return false
   100  	}
   101  	labels := co.GetLabels()
   102  	if labels == nil {
   103  		return false
   104  	}
   105  	_, ok := labels[clusterv1.MachineControlPlaneLabel]
   106  	return ok
   107  }
   108  
   109  // IsMachinePool checks if an unstructured object is a MachinePool.
   110  func (co ConfigOwner) IsMachinePool() bool {
   111  	return co.GetKind() == "MachinePool"
   112  }
   113  
   114  // KubernetesVersion returns the Kuberentes version for the config owner object.
   115  func (co ConfigOwner) KubernetesVersion() string {
   116  	fields := []string{"spec", "version"}
   117  	if co.IsMachinePool() {
   118  		fields = []string{"spec", "template", "spec", "version"}
   119  	}
   120  
   121  	version, _, err := unstructured.NestedString(co.Object, fields...)
   122  	if err != nil {
   123  		return ""
   124  	}
   125  	return version
   126  }
   127  
   128  // GetConfigOwner returns the Unstructured object owning the current resource
   129  // using the uncached unstructured client. For performance-sensitive uses,
   130  // consider GetTypedConfigOwner.
   131  func GetConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
   132  	return getConfigOwner(ctx, c, obj, GetOwnerByRef)
   133  }
   134  
   135  // GetTypedConfigOwner returns the Unstructured object owning the current
   136  // resource. The implementation ensures a typed client is used, so the objects are read from the cache.
   137  func GetTypedConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
   138  	return getConfigOwner(ctx, c, obj, GetTypedOwnerByRef)
   139  }
   140  
   141  func getConfigOwner(ctx context.Context, c client.Client, obj metav1.Object, getFn func(context.Context, client.Client, *corev1.ObjectReference) (*ConfigOwner, error)) (*ConfigOwner, error) {
   142  	allowedGKs := []schema.GroupKind{
   143  		{
   144  			Group: clusterv1.GroupVersion.Group,
   145  			Kind:  "Machine",
   146  		},
   147  	}
   148  
   149  	if feature.Gates.Enabled(feature.MachinePool) {
   150  		allowedGKs = append(allowedGKs, schema.GroupKind{
   151  			Group: expv1.GroupVersion.Group,
   152  			Kind:  "MachinePool",
   153  		})
   154  	}
   155  
   156  	for _, ref := range obj.GetOwnerReferences() {
   157  		refGV, err := schema.ParseGroupVersion(ref.APIVersion)
   158  		if err != nil {
   159  			return nil, errors.Wrapf(err, "failed to parse GroupVersion from %q", ref.APIVersion)
   160  		}
   161  		refGVK := refGV.WithKind(ref.Kind)
   162  
   163  		for _, gk := range allowedGKs {
   164  			if refGVK.Group == gk.Group && refGVK.Kind == gk.Kind {
   165  				return getFn(ctx, c, &corev1.ObjectReference{
   166  					APIVersion: ref.APIVersion,
   167  					Kind:       ref.Kind,
   168  					Name:       ref.Name,
   169  					Namespace:  obj.GetNamespace(),
   170  				})
   171  			}
   172  		}
   173  	}
   174  	return nil, nil
   175  }
   176  
   177  // GetOwnerByRef finds and returns the owner by looking at the object reference.
   178  func GetOwnerByRef(ctx context.Context, c client.Client, ref *corev1.ObjectReference) (*ConfigOwner, error) {
   179  	obj, err := external.Get(ctx, c, ref, ref.Namespace)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return &ConfigOwner{obj}, nil
   184  }
   185  
   186  // GetTypedOwnerByRef finds and returns the owner by looking at the object
   187  // reference. The implementation ensures a typed client is used, so the objects are read from the cache.
   188  func GetTypedOwnerByRef(ctx context.Context, c client.Client, ref *corev1.ObjectReference) (*ConfigOwner, error) {
   189  	objGVK := ref.GroupVersionKind()
   190  	obj, err := c.Scheme().New(objGVK)
   191  	if err != nil {
   192  		return nil, errors.Wrapf(err, "failed to construct object of type %s", ref.GroupVersionKind())
   193  	}
   194  	clientObj, ok := obj.(client.Object)
   195  	if !ok {
   196  		return nil, errors.Errorf("expected owner reference to refer to a client.Object, is actually %T", obj)
   197  	}
   198  	key := types.NamespacedName{
   199  		Namespace: ref.Namespace,
   200  		Name:      ref.Name,
   201  	}
   202  	err = c.Get(ctx, key, clientObj)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clientObj)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	u := unstructured.Unstructured{}
   212  	u.SetUnstructuredContent(content)
   213  	u.SetGroupVersionKind(objGVK)
   214  
   215  	return &ConfigOwner{&u}, nil
   216  }