sigs.k8s.io/cluster-api@v1.7.1/controllers/external/util.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 external 18 19 import ( 20 "context" 21 "strings" 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/apiserver/pkg/storage/names" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 30 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 31 ) 32 33 // Get uses the client and reference to get an external, unstructured object. 34 func Get(ctx context.Context, c client.Reader, ref *corev1.ObjectReference, namespace string) (*unstructured.Unstructured, error) { 35 if ref == nil { 36 return nil, errors.Errorf("cannot get object - object reference not set") 37 } 38 obj := new(unstructured.Unstructured) 39 obj.SetAPIVersion(ref.APIVersion) 40 obj.SetKind(ref.Kind) 41 obj.SetName(ref.Name) 42 key := client.ObjectKey{Name: obj.GetName(), Namespace: namespace} 43 if err := c.Get(ctx, key, obj); err != nil { 44 return nil, errors.Wrapf(err, "failed to retrieve %s external object %q/%q", obj.GetKind(), key.Namespace, key.Name) 45 } 46 return obj, nil 47 } 48 49 // Delete uses the client and reference to delete an external, unstructured object. 50 func Delete(ctx context.Context, c client.Writer, ref *corev1.ObjectReference) error { 51 obj := new(unstructured.Unstructured) 52 obj.SetAPIVersion(ref.APIVersion) 53 obj.SetKind(ref.Kind) 54 obj.SetName(ref.Name) 55 obj.SetNamespace(ref.Namespace) 56 if err := c.Delete(ctx, obj); err != nil { 57 return errors.Wrapf(err, "failed to delete %s external object %q/%q", obj.GetKind(), obj.GetNamespace(), obj.GetName()) 58 } 59 return nil 60 } 61 62 // CreateFromTemplateInput is the input to CreateFromTemplate. 63 type CreateFromTemplateInput struct { 64 // Client is the controller runtime client. 65 Client client.Client 66 67 // TemplateRef is a reference to the template that needs to be cloned. 68 TemplateRef *corev1.ObjectReference 69 70 // Namespace is the Kubernetes namespace the cloned object should be created into. 71 Namespace string 72 73 // Name is used as the name of the generated object, if set. 74 // If it isn't set the template name will be used as prefix to generate a name instead. 75 Name string 76 77 // ClusterName is the cluster this object is linked to. 78 ClusterName string 79 80 // OwnerRef is an optional OwnerReference to attach to the cloned object. 81 // +optional 82 OwnerRef *metav1.OwnerReference 83 84 // Labels is an optional map of labels to be added to the object. 85 // +optional 86 Labels map[string]string 87 88 // Annotations is an optional map of annotations to be added to the object. 89 // +optional 90 Annotations map[string]string 91 } 92 93 // CreateFromTemplate uses the client and the reference to create a new object from the template. 94 func CreateFromTemplate(ctx context.Context, in *CreateFromTemplateInput) (*corev1.ObjectReference, error) { 95 from, err := Get(ctx, in.Client, in.TemplateRef, in.Namespace) 96 if err != nil { 97 return nil, err 98 } 99 generateTemplateInput := &GenerateTemplateInput{ 100 Template: from, 101 TemplateRef: in.TemplateRef, 102 Namespace: in.Namespace, 103 Name: in.Name, 104 ClusterName: in.ClusterName, 105 OwnerRef: in.OwnerRef, 106 Labels: in.Labels, 107 Annotations: in.Annotations, 108 } 109 to, err := GenerateTemplate(generateTemplateInput) 110 if err != nil { 111 return nil, err 112 } 113 114 // Create the external clone. 115 if err := in.Client.Create(ctx, to); err != nil { 116 return nil, err 117 } 118 119 return GetObjectReference(to), nil 120 } 121 122 // GenerateTemplateInput is the input needed to generate a new template. 123 type GenerateTemplateInput struct { 124 // Template is the TemplateRef turned into an unstructured. 125 Template *unstructured.Unstructured 126 127 // TemplateRef is a reference to the template that needs to be cloned. 128 TemplateRef *corev1.ObjectReference 129 130 // Namespace is the Kubernetes namespace the cloned object should be created into. 131 Namespace string 132 133 // Name is used as the name of the generated object, if set. 134 // If it isn't set the template name will be used as prefix to generate a name instead. 135 Name string 136 137 // ClusterName is the cluster this object is linked to. 138 ClusterName string 139 140 // OwnerRef is an optional OwnerReference to attach to the cloned object. 141 // +optional 142 OwnerRef *metav1.OwnerReference 143 144 // Labels is an optional map of labels to be added to the object. 145 // +optional 146 Labels map[string]string 147 148 // Annotations is an optional map of annotations to be added to the object. 149 // +optional 150 Annotations map[string]string 151 } 152 153 // GenerateTemplate generates an object with the given template input. 154 func GenerateTemplate(in *GenerateTemplateInput) (*unstructured.Unstructured, error) { 155 template, found, err := unstructured.NestedMap(in.Template.Object, "spec", "template") 156 if !found { 157 return nil, errors.Errorf("missing Spec.Template on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) 158 } else if err != nil { 159 return nil, errors.Wrapf(err, "failed to retrieve Spec.Template map on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) 160 } 161 162 // Create the unstructured object from the template. 163 to := &unstructured.Unstructured{Object: template} 164 to.SetResourceVersion("") 165 to.SetFinalizers(nil) 166 to.SetUID("") 167 to.SetSelfLink("") 168 to.SetName(in.Name) 169 if to.GetName() == "" { 170 to.SetName(names.SimpleNameGenerator.GenerateName(in.Template.GetName() + "-")) 171 } 172 to.SetNamespace(in.Namespace) 173 174 // Set annotations. 175 annotations := to.GetAnnotations() 176 if annotations == nil { 177 annotations = map[string]string{} 178 } 179 for key, value := range in.Annotations { 180 annotations[key] = value 181 } 182 annotations[clusterv1.TemplateClonedFromNameAnnotation] = in.TemplateRef.Name 183 annotations[clusterv1.TemplateClonedFromGroupKindAnnotation] = in.TemplateRef.GroupVersionKind().GroupKind().String() 184 to.SetAnnotations(annotations) 185 186 // Set labels. 187 labels := to.GetLabels() 188 if labels == nil { 189 labels = map[string]string{} 190 } 191 for key, value := range in.Labels { 192 labels[key] = value 193 } 194 labels[clusterv1.ClusterNameLabel] = in.ClusterName 195 to.SetLabels(labels) 196 197 // Set the owner reference. 198 if in.OwnerRef != nil { 199 to.SetOwnerReferences([]metav1.OwnerReference{*in.OwnerRef}) 200 } 201 202 // Set the object APIVersion. 203 if to.GetAPIVersion() == "" { 204 to.SetAPIVersion(in.Template.GetAPIVersion()) 205 } 206 207 // Set the object Kind and strip the word "Template" if it's a suffix. 208 if to.GetKind() == "" { 209 to.SetKind(strings.TrimSuffix(in.Template.GetKind(), clusterv1.TemplateSuffix)) 210 } 211 return to, nil 212 } 213 214 // GetObjectReference converts an unstructured into object reference. 215 func GetObjectReference(obj *unstructured.Unstructured) *corev1.ObjectReference { 216 return &corev1.ObjectReference{ 217 APIVersion: obj.GetAPIVersion(), 218 Kind: obj.GetKind(), 219 Name: obj.GetName(), 220 Namespace: obj.GetNamespace(), 221 UID: obj.GetUID(), 222 } 223 } 224 225 // FailuresFrom returns the FailureReason and FailureMessage fields from the external object status. 226 func FailuresFrom(obj *unstructured.Unstructured) (string, string, error) { 227 failureReason, _, err := unstructured.NestedString(obj.Object, "status", "failureReason") 228 if err != nil { 229 return "", "", errors.Wrapf(err, "failed to determine failureReason on %v %q", 230 obj.GroupVersionKind(), obj.GetName()) 231 } 232 failureMessage, _, err := unstructured.NestedString(obj.Object, "status", "failureMessage") 233 if err != nil { 234 return "", "", errors.Wrapf(err, "failed to determine failureMessage on %v %q", 235 obj.GroupVersionKind(), obj.GetName()) 236 } 237 return failureReason, failureMessage, nil 238 } 239 240 // IsReady returns true if the Status.Ready field on an external object is true. 241 func IsReady(obj *unstructured.Unstructured) (bool, error) { 242 ready, found, err := unstructured.NestedBool(obj.Object, "status", "ready") 243 if err != nil { 244 return false, errors.Wrapf(err, "failed to determine %v %q readiness", 245 obj.GroupVersionKind(), obj.GetName()) 246 } 247 return ready && found, nil 248 } 249 250 // IsInitialized returns true if the Status.Initialized field on an external object is true. 251 func IsInitialized(obj *unstructured.Unstructured) (bool, error) { 252 initialized, found, err := unstructured.NestedBool(obj.Object, "status", "initialized") 253 if err != nil { 254 return false, errors.Wrapf(err, "failed to determine %v %q initialized", 255 obj.GroupVersionKind(), obj.GetName()) 256 } 257 return initialized && found, nil 258 }