sigs.k8s.io/cluster-api@v1.6.3/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 // ClusterName is the cluster this object is linked to. 74 ClusterName string 75 76 // OwnerRef is an optional OwnerReference to attach to the cloned object. 77 // +optional 78 OwnerRef *metav1.OwnerReference 79 80 // Labels is an optional map of labels to be added to the object. 81 // +optional 82 Labels map[string]string 83 84 // Annotations is an optional map of annotations to be added to the object. 85 // +optional 86 Annotations map[string]string 87 } 88 89 // CreateFromTemplate uses the client and the reference to create a new object from the template. 90 func CreateFromTemplate(ctx context.Context, in *CreateFromTemplateInput) (*corev1.ObjectReference, error) { 91 from, err := Get(ctx, in.Client, in.TemplateRef, in.Namespace) 92 if err != nil { 93 return nil, err 94 } 95 generateTemplateInput := &GenerateTemplateInput{ 96 Template: from, 97 TemplateRef: in.TemplateRef, 98 Namespace: in.Namespace, 99 ClusterName: in.ClusterName, 100 OwnerRef: in.OwnerRef, 101 Labels: in.Labels, 102 Annotations: in.Annotations, 103 } 104 to, err := GenerateTemplate(generateTemplateInput) 105 if err != nil { 106 return nil, err 107 } 108 109 // Create the external clone. 110 if err := in.Client.Create(ctx, to); err != nil { 111 return nil, err 112 } 113 114 return GetObjectReference(to), nil 115 } 116 117 // GenerateTemplateInput is the input needed to generate a new template. 118 type GenerateTemplateInput struct { 119 // Template is the TemplateRef turned into an unstructured. 120 Template *unstructured.Unstructured 121 122 // TemplateRef is a reference to the template that needs to be cloned. 123 TemplateRef *corev1.ObjectReference 124 125 // Namespace is the Kubernetes namespace the cloned object should be created into. 126 Namespace string 127 128 // ClusterName is the cluster this object is linked to. 129 ClusterName string 130 131 // OwnerRef is an optional OwnerReference to attach to the cloned object. 132 // +optional 133 OwnerRef *metav1.OwnerReference 134 135 // Labels is an optional map of labels to be added to the object. 136 // +optional 137 Labels map[string]string 138 139 // Annotations is an optional map of annotations to be added to the object. 140 // +optional 141 Annotations map[string]string 142 } 143 144 // GenerateTemplate generates an object with the given template input. 145 func GenerateTemplate(in *GenerateTemplateInput) (*unstructured.Unstructured, error) { 146 template, found, err := unstructured.NestedMap(in.Template.Object, "spec", "template") 147 if !found { 148 return nil, errors.Errorf("missing Spec.Template on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) 149 } else if err != nil { 150 return nil, errors.Wrapf(err, "failed to retrieve Spec.Template map on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) 151 } 152 153 // Create the unstructured object from the template. 154 to := &unstructured.Unstructured{Object: template} 155 to.SetResourceVersion("") 156 to.SetFinalizers(nil) 157 to.SetUID("") 158 to.SetSelfLink("") 159 to.SetName(names.SimpleNameGenerator.GenerateName(in.Template.GetName() + "-")) 160 to.SetNamespace(in.Namespace) 161 162 // Set annotations. 163 annotations := to.GetAnnotations() 164 if annotations == nil { 165 annotations = map[string]string{} 166 } 167 for key, value := range in.Annotations { 168 annotations[key] = value 169 } 170 annotations[clusterv1.TemplateClonedFromNameAnnotation] = in.TemplateRef.Name 171 annotations[clusterv1.TemplateClonedFromGroupKindAnnotation] = in.TemplateRef.GroupVersionKind().GroupKind().String() 172 to.SetAnnotations(annotations) 173 174 // Set labels. 175 labels := to.GetLabels() 176 if labels == nil { 177 labels = map[string]string{} 178 } 179 for key, value := range in.Labels { 180 labels[key] = value 181 } 182 labels[clusterv1.ClusterNameLabel] = in.ClusterName 183 to.SetLabels(labels) 184 185 // Set the owner reference. 186 if in.OwnerRef != nil { 187 to.SetOwnerReferences([]metav1.OwnerReference{*in.OwnerRef}) 188 } 189 190 // Set the object APIVersion. 191 if to.GetAPIVersion() == "" { 192 to.SetAPIVersion(in.Template.GetAPIVersion()) 193 } 194 195 // Set the object Kind and strip the word "Template" if it's a suffix. 196 if to.GetKind() == "" { 197 to.SetKind(strings.TrimSuffix(in.Template.GetKind(), clusterv1.TemplateSuffix)) 198 } 199 return to, nil 200 } 201 202 // GetObjectReference converts an unstructured into object reference. 203 func GetObjectReference(obj *unstructured.Unstructured) *corev1.ObjectReference { 204 return &corev1.ObjectReference{ 205 APIVersion: obj.GetAPIVersion(), 206 Kind: obj.GetKind(), 207 Name: obj.GetName(), 208 Namespace: obj.GetNamespace(), 209 UID: obj.GetUID(), 210 } 211 } 212 213 // FailuresFrom returns the FailureReason and FailureMessage fields from the external object status. 214 func FailuresFrom(obj *unstructured.Unstructured) (string, string, error) { 215 failureReason, _, err := unstructured.NestedString(obj.Object, "status", "failureReason") 216 if err != nil { 217 return "", "", errors.Wrapf(err, "failed to determine failureReason on %v %q", 218 obj.GroupVersionKind(), obj.GetName()) 219 } 220 failureMessage, _, err := unstructured.NestedString(obj.Object, "status", "failureMessage") 221 if err != nil { 222 return "", "", errors.Wrapf(err, "failed to determine failureMessage on %v %q", 223 obj.GroupVersionKind(), obj.GetName()) 224 } 225 return failureReason, failureMessage, nil 226 } 227 228 // IsReady returns true if the Status.Ready field on an external object is true. 229 func IsReady(obj *unstructured.Unstructured) (bool, error) { 230 ready, found, err := unstructured.NestedBool(obj.Object, "status", "ready") 231 if err != nil { 232 return false, errors.Wrapf(err, "failed to determine %v %q readiness", 233 obj.GroupVersionKind(), obj.GetName()) 234 } 235 return ready && found, nil 236 } 237 238 // IsInitialized returns true if the Status.Initialized field on an external object is true. 239 func IsInitialized(obj *unstructured.Unstructured) (bool, error) { 240 initialized, found, err := unstructured.NestedBool(obj.Object, "status", "initialized") 241 if err != nil { 242 return false, errors.Wrapf(err, "failed to determine %v %q initialized", 243 obj.GroupVersionKind(), obj.GetName()) 244 } 245 return initialized && found, nil 246 }