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  }