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  }