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 }