github.com/oam-dev/kubevela@v1.9.11/pkg/oam/util/helper.go (about)

     1  /*
     2  Copyright 2021 The KubeVela 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
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"hash"
    24  	"hash/fnv"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/davecgh/go-spew/spew"
    29  	"github.com/pkg/errors"
    30  	corev1 "k8s.io/api/core/v1"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	"k8s.io/apimachinery/pkg/api/meta"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	"k8s.io/apimachinery/pkg/runtime/schema"
    37  	"k8s.io/apimachinery/pkg/types"
    38  	"k8s.io/apimachinery/pkg/util/rand"
    39  	"k8s.io/apimachinery/pkg/util/validation"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
    44  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    45  	types2 "github.com/oam-dev/kubevela/apis/types"
    46  	"github.com/oam-dev/kubevela/pkg/oam"
    47  )
    48  
    49  const (
    50  	// TraitPrefixKey is prefix of trait name
    51  	TraitPrefixKey = "trait"
    52  
    53  	// Dummy used for dummy definition
    54  	Dummy = "dummy"
    55  
    56  	// DummyTraitMessage is a message for trait which don't have definition found
    57  	DummyTraitMessage = "No TraitDefinition found, all framework capabilities will work as default"
    58  )
    59  
    60  const (
    61  	// ErrReconcileErrInCondition indicates one or more error occurs and are recorded in status conditions
    62  	ErrReconcileErrInCondition = "object level reconcile error, type: %q, msg: %q"
    63  	// ErrUpdateStatus is the error while applying status.
    64  	ErrUpdateStatus = "cannot apply status"
    65  
    66  	// ErrStoreCapabilityInConfigMap is the error while storing capability in ConfigMap
    67  	ErrStoreCapabilityInConfigMap = "cannot store capability %s in ConfigMap: %v"
    68  	// ErrGenerateOpenAPIV2JSONSchemaForCapability is the error while generating OpenAPI v3 schema
    69  	ErrGenerateOpenAPIV2JSONSchemaForCapability = "cannot generate OpenAPI v3 JSON schema for capability %s: %v"
    70  	// ErrUpdateCapabilityInConfigMap is the error while creating or updating a capability
    71  	ErrUpdateCapabilityInConfigMap = "cannot create or update capability %s in ConfigMap: %v"
    72  
    73  	// ErrUpdateComponentDefinition is the error while update ComponentDefinition
    74  	ErrUpdateComponentDefinition = "cannot update ComponentDefinition %s: %v"
    75  	// ErrUpdateTraitDefinition is the error while update TraitDefinition
    76  	ErrUpdateTraitDefinition = "cannot update TraitDefinition %s: %v"
    77  	// ErrUpdateStepDefinition is the error while update WorkflowStepDefinition
    78  	ErrUpdateStepDefinition = "cannot update WorkflowStepDefinition %s: %v"
    79  	// ErrUpdatePolicyDefinition is the error while update PolicyDefinition
    80  	ErrUpdatePolicyDefinition = "cannot update PolicyDefinition %s: %v"
    81  	// ErrUpdateWorkflowStepDefinition is the error while update WorkflowStepDefinition
    82  	ErrUpdateWorkflowStepDefinition = "cannot update WorkflowStepDefinition %s: %v"
    83  
    84  	// ErrCreateConvertedWorklaodDefinition is the error while apply a WorkloadDefinition
    85  	ErrCreateConvertedWorklaodDefinition = "cannot create converted WorkloadDefinition %s: %v"
    86  
    87  	// ErrRefreshPackageDiscover is the error while refresh PackageDiscover
    88  	ErrRefreshPackageDiscover = "cannot discover the open api of the CRD : %v"
    89  
    90  	// ErrGenerateDefinitionRevision is the error while generate DefinitionRevision
    91  	ErrGenerateDefinitionRevision = "cannot generate DefinitionRevision of %s: %v"
    92  	// ErrCreateDefinitionRevision is the error while create or update DefinitionRevision
    93  	ErrCreateDefinitionRevision = "cannot create DefinitionRevision %s: %v"
    94  )
    95  
    96  // WorkloadType describe the workload type of ComponentDefinition
    97  type WorkloadType string
    98  
    99  const (
   100  	// ComponentDef describe a workload of Defined by ComponentDefinition
   101  	ComponentDef WorkloadType = "ComponentDef"
   102  
   103  	// KubeDef describe a workload refer to raw K8s resource
   104  	KubeDef WorkloadType = "KubeDef"
   105  
   106  	// HELMDef describe a workload refer to HELM
   107  	HELMDef WorkloadType = "HelmDef"
   108  
   109  	// TerraformDef describes a workload refer to Terraform
   110  	TerraformDef WorkloadType = "TerraformDef"
   111  
   112  	// ReferWorkload describe an existing workload
   113  	ReferWorkload WorkloadType = "ReferWorkload"
   114  )
   115  
   116  type namespaceContextKey int
   117  
   118  const (
   119  	// AppDefinitionNamespace is context key to define app namespace
   120  	AppDefinitionNamespace namespaceContextKey = iota
   121  	// XDefinitionNamespace is context key to define the namespace, which x-definition(Component/Trait) is installed to
   122  	XDefinitionNamespace
   123  )
   124  
   125  // A ConditionedObject is an Object type with condition field
   126  type ConditionedObject interface {
   127  	client.Object
   128  
   129  	oam.Conditioned
   130  }
   131  
   132  // ErrBadRevision represents an error when the revision name is not standardized
   133  const ErrBadRevision = "bad revision name"
   134  
   135  // GetDefinitionNamespaceWithCtx will get namespace from context, it will try get `AppDefinitionNamespace` key, if not found,
   136  // will use default system level namespace defined in `systemvar.SystemDefinitionNamespace`
   137  func GetDefinitionNamespaceWithCtx(ctx context.Context) string {
   138  	var appNs string
   139  	if app := ctx.Value(AppDefinitionNamespace); app == nil {
   140  		appNs = oam.SystemDefinitionNamespace
   141  	} else {
   142  		appNs = app.(string)
   143  	}
   144  	return appNs
   145  }
   146  
   147  // GetXDefinitionNamespaceWithCtx will get namespace from context, it will try get `XDefinitionNamespace` key, if not found,
   148  // will use default system level namespace defined in `vela-system`
   149  func GetXDefinitionNamespaceWithCtx(ctx context.Context) string {
   150  	if xNs, _ := ctx.Value(XDefinitionNamespace).(string); len(xNs) > 0 {
   151  		return xNs
   152  	}
   153  	return oam.SystemDefinitionNamespace
   154  }
   155  
   156  // SetNamespaceInCtx set app namespace in context,
   157  // Sometimes webhook handler may receive a request that appNs is empty string, and will cause error when search definition
   158  // So if namespace is empty, it will use `default` namespace by default.
   159  func SetNamespaceInCtx(ctx context.Context, namespace string) context.Context {
   160  	if namespace == "" {
   161  		// compatible with some webhook handlers that maybe receive empty string as app namespace which means `default` namespace
   162  		namespace = types2.DefaultAppNamespace
   163  	}
   164  	ctx = context.WithValue(ctx, AppDefinitionNamespace, namespace)
   165  	return ctx
   166  }
   167  
   168  // SetXDefinitionNamespaceInCtx set x-definition namespace in context,
   169  // Sometimes x-definition is installed to customized namespace
   170  // So it is empty, it will use `vela-system` namespace by default.
   171  func SetXDefinitionNamespaceInCtx(ctx context.Context, namespace string) context.Context {
   172  	if namespace == "" {
   173  		namespace = oam.SystemDefinitionNamespace
   174  	}
   175  	ctx = context.WithValue(ctx, XDefinitionNamespace, namespace)
   176  	return ctx
   177  }
   178  
   179  // GetDefinition get definition from two level namespace
   180  func GetDefinition(ctx context.Context, cli client.Reader, definition client.Object, definitionName string) error {
   181  	appNs := GetDefinitionNamespaceWithCtx(ctx)
   182  	if err := cli.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: appNs}, definition); err != nil {
   183  		if !apierrors.IsNotFound(err) {
   184  			return err
   185  		}
   186  
   187  		for _, ns := range []string{GetXDefinitionNamespaceWithCtx(ctx), oam.SystemDefinitionNamespace} {
   188  			err = GetDefinitionFromNamespace(ctx, cli, definition, definitionName, ns)
   189  			if !apierrors.IsNotFound(err) {
   190  				return err
   191  			}
   192  		}
   193  		return err
   194  	}
   195  	return nil
   196  }
   197  
   198  // GetDefinitionFromNamespace get definition from namespace.
   199  func GetDefinitionFromNamespace(ctx context.Context, cli client.Reader, definition client.Object, definitionName, namespace string) error {
   200  	if err := cli.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: namespace}, definition); err != nil {
   201  		if apierrors.IsNotFound(err) {
   202  			// compatibility code for old clusters those definition crd is cluster scope
   203  			var newErr error
   204  			if newErr = cli.Get(ctx, types.NamespacedName{Name: definitionName}, definition); checkRequestNamespaceError(newErr) {
   205  				return err
   206  			}
   207  			return newErr
   208  		}
   209  		return err
   210  	}
   211  	return nil
   212  }
   213  
   214  // GetCapabilityDefinition can get different versions of ComponentDefinition/TraitDefinition
   215  func GetCapabilityDefinition(ctx context.Context, cli client.Reader, definition client.Object,
   216  	definitionName string) error {
   217  	isLatestRevision, defRev, err := fetchDefinitionRev(ctx, cli, definitionName)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	if isLatestRevision {
   222  		return GetDefinition(ctx, cli, definition, definitionName)
   223  	}
   224  	switch def := definition.(type) {
   225  	case *v1beta1.ComponentDefinition:
   226  		*def = defRev.Spec.ComponentDefinition
   227  	case *v1beta1.TraitDefinition:
   228  		*def = defRev.Spec.TraitDefinition
   229  	case *v1beta1.PolicyDefinition:
   230  		*def = defRev.Spec.PolicyDefinition
   231  	case *v1beta1.WorkflowStepDefinition:
   232  		*def = defRev.Spec.WorkflowStepDefinition
   233  	default:
   234  	}
   235  	return nil
   236  }
   237  
   238  func fetchDefinitionRev(ctx context.Context, cli client.Reader, definitionName string) (bool, *v1beta1.DefinitionRevision, error) {
   239  	// if the component's type doesn't contain '@' means user want to use the latest Definition.
   240  	if !strings.Contains(definitionName, "@") {
   241  		return true, nil, nil
   242  	}
   243  
   244  	defRevName, err := ConvertDefinitionRevName(definitionName)
   245  	if err != nil {
   246  		return false, nil, err
   247  	}
   248  	defRev := new(v1beta1.DefinitionRevision)
   249  	if err := GetDefinition(ctx, cli, defRev, defRevName); err != nil {
   250  		return false, nil, err
   251  	}
   252  	return false, defRev, nil
   253  }
   254  
   255  // ConvertDefinitionRevName can help convert definition type defined in Application to DefinitionRevision Name
   256  // e.g., worker@v1.3.1 will be convert to worker-v1.3.1
   257  func ConvertDefinitionRevName(definitionName string) (string, error) {
   258  	splits := strings.Split(definitionName, "@v")
   259  	if len(splits) == 1 || len(splits[0]) == 0 {
   260  		errs := validation.IsQualifiedName(definitionName)
   261  		if len(errs) != 0 {
   262  			return definitionName, errors.Errorf("invalid definitionRevision name %s:%s", definitionName, strings.Join(errs, ","))
   263  		}
   264  		return definitionName, nil
   265  	}
   266  
   267  	defName := splits[0]
   268  	revisionName := strings.TrimPrefix(definitionName, fmt.Sprintf("%s@v", defName))
   269  	defRevName := fmt.Sprintf("%s-v%s", defName, revisionName)
   270  	errs := validation.IsQualifiedName(defRevName)
   271  	if len(errs) != 0 {
   272  		return defRevName, errors.Errorf("invalid definitionRevision name %s:%s", defName, strings.Join(errs, ","))
   273  	}
   274  	return defRevName, nil
   275  }
   276  
   277  // when get a namespaced scope object without namespace, would get an error request namespace
   278  func checkRequestNamespaceError(err error) bool {
   279  	return err != nil && err.Error() == "an empty namespace may not be set when a resource name is provided"
   280  }
   281  
   282  // EndReconcileWithNegativeCondition is used to handle reconcile failure for a conditioned resource.
   283  // It will make ctrl-mgr to requeue the resource through patching changed conditions or returning
   284  // an error.
   285  // It should not handle reconcile success with positive conditions, otherwise it will trigger
   286  // infinite requeue.
   287  func EndReconcileWithNegativeCondition(ctx context.Context, r client.StatusClient, workload ConditionedObject,
   288  	condition ...condition.Condition) error {
   289  	if len(condition) == 0 {
   290  		return nil
   291  	}
   292  	workloadPatch := client.MergeFrom(workload.DeepCopyObject().(client.Object))
   293  	conditionIsChanged := IsConditionChanged(condition, workload)
   294  	workload.SetConditions(condition...)
   295  	if err := r.Status().Patch(ctx, workload, workloadPatch, client.FieldOwner(workload.GetUID())); err != nil {
   296  		return errors.Wrap(err, ErrUpdateStatus)
   297  	}
   298  	if conditionIsChanged {
   299  		// if any condition is changed, patching status can trigger requeue the resource and we should return nil to
   300  		// avoid requeue it again
   301  		return nil
   302  	}
   303  	// if no condition is changed, patching status can not trigger requeue, so we must return an error to
   304  	// requeue the resource
   305  	return errors.Errorf(ErrReconcileErrInCondition, condition[0].Type, condition[0].Message)
   306  }
   307  
   308  // PatchCondition will patch status with condition and return, it generally used by cases which don't want to reconcile after patch
   309  func PatchCondition(ctx context.Context, r client.StatusClient, workload ConditionedObject,
   310  	condition ...condition.Condition) error {
   311  	if len(condition) == 0 {
   312  		return nil
   313  	}
   314  	workloadPatch := client.MergeFrom(workload.DeepCopyObject().(client.Object))
   315  	workload.SetConditions(condition...)
   316  	return r.Status().Patch(ctx, workload, workloadPatch, client.FieldOwner(workload.GetUID()))
   317  }
   318  
   319  // IsConditionChanged will check if conditions in workload are changed compare to newCondition
   320  func IsConditionChanged(newCondition []condition.Condition, workload ConditionedObject) bool {
   321  	var conditionIsChanged bool
   322  	for _, newCond := range newCondition {
   323  		// NOTE(roywang) an implicit rule here: condition type is unique in an object's conditions
   324  		// if this rule is changed in the future, we must revise below logic correspondingly
   325  		existingCond := workload.GetCondition(newCond.Type)
   326  
   327  		if !existingCond.Equal(newCond) {
   328  			conditionIsChanged = true
   329  			break
   330  		}
   331  	}
   332  	return conditionIsChanged
   333  }
   334  
   335  // EndReconcileWithPositiveCondition is used to handle reconcile success for a conditioned resource.
   336  // It should only accept positive condition which means no need to requeue the resource.
   337  func EndReconcileWithPositiveCondition(ctx context.Context, r client.StatusClient, workload ConditionedObject,
   338  	condition ...condition.Condition) error {
   339  	workloadPatch := client.MergeFrom(workload.DeepCopyObject().(client.Object))
   340  	workload.SetConditions(condition...)
   341  	return errors.Wrap(
   342  		r.Status().Patch(ctx, workload, workloadPatch, client.FieldOwner(workload.GetUID())),
   343  		ErrUpdateStatus)
   344  }
   345  
   346  // A metaObject is a Kubernetes object that has label and annotation
   347  type labelAnnotationObject interface {
   348  	GetLabels() map[string]string
   349  	SetLabels(labels map[string]string)
   350  	GetAnnotations() map[string]string
   351  	SetAnnotations(annotations map[string]string)
   352  }
   353  
   354  // PassLabelAndAnnotation passes through labels and annotation objectMeta from the parent to the child object
   355  // when annotation or labels has conflicts, the parentObj will override the childObj.
   356  func PassLabelAndAnnotation(parentObj, childObj labelAnnotationObject) {
   357  	// pass app-config labels
   358  	childObj.SetLabels(MergeMapOverrideWithDst(childObj.GetLabels(), parentObj.GetLabels()))
   359  	// pass app-config annotation
   360  	childObj.SetAnnotations(MergeMapOverrideWithDst(childObj.GetAnnotations(), parentObj.GetAnnotations()))
   361  }
   362  
   363  // RemoveLabels removes keys that contains in the removekeys slice from the label
   364  func RemoveLabels(o labelAnnotationObject, removeKeys []string) {
   365  	exist := o.GetLabels()
   366  	for _, key := range removeKeys {
   367  		delete(exist, key)
   368  	}
   369  	o.SetLabels(exist)
   370  }
   371  
   372  // RemoveAnnotations removes keys that contains in the removekeys slice from the annotation
   373  func RemoveAnnotations(o labelAnnotationObject, removeKeys []string) {
   374  	exist := o.GetAnnotations()
   375  	for _, key := range removeKeys {
   376  		delete(exist, key)
   377  	}
   378  	o.SetAnnotations(exist)
   379  }
   380  
   381  // GetDefinitionName return the Definition name of any resources
   382  // the format of the definition of a resource is <kind plurals>.<group>
   383  // Now the definition name of a resource could also be defined as `definition.oam.dev/name` in `metadata.annotations`
   384  // typeLabel specified which Definition it is, if specified, will directly get definition from label.
   385  func GetDefinitionName(mapper meta.RESTMapper, u *unstructured.Unstructured, typeLabel string) (string, error) {
   386  	if typeLabel != "" {
   387  		if labels := u.GetLabels(); labels != nil {
   388  			if definitionName, ok := labels[typeLabel]; ok {
   389  				return definitionName, nil
   390  			}
   391  		}
   392  	}
   393  	groupVersion, err := schema.ParseGroupVersion(u.GetAPIVersion())
   394  	if err != nil {
   395  		return "", err
   396  	}
   397  	mapping, err := mapper.RESTMapping(schema.GroupKind{Group: groupVersion.Group, Kind: u.GetKind()}, groupVersion.Version)
   398  	if err != nil {
   399  		return "", err
   400  	}
   401  	return mapping.Resource.Resource + "." + groupVersion.Group, nil
   402  }
   403  
   404  // GetGVKFromDefinition help get Group Version Kind from DefinitionReference
   405  func GetGVKFromDefinition(mapper meta.RESTMapper, definitionRef common.DefinitionReference) (metav1.GroupVersionKind, error) {
   406  	// if given definitionRef is empty or it's a dummy definition, return an empty GVK
   407  	// NOTE currently, only TraitDefinition is allowed to omit definitionRef conditionally.
   408  	if len(definitionRef.Name) < 1 || definitionRef.Name == Dummy {
   409  		return metav1.GroupVersionKind{}, nil
   410  	}
   411  	var gvk metav1.GroupVersionKind
   412  	groupResource := schema.ParseGroupResource(definitionRef.Name)
   413  	gvr := schema.GroupVersionResource{Group: groupResource.Group, Resource: groupResource.Resource, Version: definitionRef.Version}
   414  	kinds, err := mapper.KindsFor(gvr)
   415  	if err != nil {
   416  		return gvk, err
   417  	}
   418  	if len(kinds) < 1 {
   419  		return gvk, &meta.NoResourceMatchError{
   420  			PartialResource: gvr,
   421  		}
   422  	}
   423  	return metav1.GroupVersionKind{
   424  		Group:   kinds[0].Group,
   425  		Kind:    kinds[0].Kind,
   426  		Version: kinds[0].Version,
   427  	}, nil
   428  }
   429  
   430  // ConvertWorkloadGVK2Definition help convert a GVK to DefinitionReference
   431  func ConvertWorkloadGVK2Definition(mapper meta.RESTMapper, def common.WorkloadGVK) (common.DefinitionReference, error) {
   432  	var reference common.DefinitionReference
   433  	gv, err := schema.ParseGroupVersion(def.APIVersion)
   434  	if err != nil {
   435  		return reference, err
   436  	}
   437  	gvk := gv.WithKind(def.Kind)
   438  	mappings, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   439  	if err != nil {
   440  		return reference, err
   441  	}
   442  	gvr := mappings.Resource
   443  	reference.Version = gvr.Version
   444  	reference.Name = gvr.GroupResource().String()
   445  	return reference, nil
   446  }
   447  
   448  // GetObjectsGivenGVKAndLabels fetches the kubernetes object given its gvk and labels by list API
   449  func GetObjectsGivenGVKAndLabels(ctx context.Context, cli client.Reader,
   450  	gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*unstructured.UnstructuredList, error) {
   451  	unstructuredObjList := &unstructured.UnstructuredList{}
   452  	apiVersion := metav1.GroupVersion{
   453  		Group:   gvk.Group,
   454  		Version: gvk.Version,
   455  	}.String()
   456  	unstructuredObjList.SetAPIVersion(apiVersion)
   457  	unstructuredObjList.SetKind(gvk.Kind)
   458  	if err := cli.List(ctx, unstructuredObjList, client.MatchingLabels(labels), client.InNamespace(namespace)); err != nil {
   459  		return nil, errors.Wrap(err, fmt.Sprintf("failed to get obj with labels %+v and gvk %+v ", labels, gvk))
   460  	}
   461  	return unstructuredObjList, nil
   462  }
   463  
   464  // GetObjectGivenGVKAndName fetches the kubernetes object given its gvk and name
   465  func GetObjectGivenGVKAndName(ctx context.Context, client client.Reader,
   466  	gvk schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) {
   467  	obj := &unstructured.Unstructured{}
   468  	apiVersion := metav1.GroupVersion{
   469  		Group:   gvk.Group,
   470  		Version: gvk.Version,
   471  	}.String()
   472  	obj.SetAPIVersion(apiVersion)
   473  	obj.SetKind(gvk.Kind)
   474  	err := client.Get(ctx, types.NamespacedName{
   475  		Namespace: namespace,
   476  		Name:      name},
   477  		obj)
   478  	if err != nil {
   479  		return nil, errors.Wrap(err, fmt.Sprintf("failed to get obj %s with gvk %+v ", name, gvk))
   480  	}
   481  	return obj, nil
   482  }
   483  
   484  // Object2Unstructured converts an object to an unstructured struct
   485  func Object2Unstructured(obj interface{}) (*unstructured.Unstructured, error) {
   486  	objMap, err := Object2Map(obj)
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	return &unstructured.Unstructured{
   491  		Object: objMap,
   492  	}, nil
   493  }
   494  
   495  // RawExtension2Unstructured converts a rawExtension to an unstructured struct
   496  func RawExtension2Unstructured(raw *runtime.RawExtension) (*unstructured.Unstructured, error) {
   497  	var objMap map[string]interface{}
   498  	err := json.Unmarshal(raw.Raw, &objMap)
   499  	if err != nil {
   500  		return nil, err
   501  	}
   502  	return &unstructured.Unstructured{
   503  		Object: objMap,
   504  	}, nil
   505  }
   506  
   507  // RawExtension2Application converts runtime.RawExtension to Application
   508  func RawExtension2Application(raw runtime.RawExtension) (*v1beta1.Application, error) {
   509  	a := &v1beta1.Application{}
   510  	b, err := raw.MarshalJSON()
   511  	if err != nil {
   512  		return nil, err
   513  	}
   514  	if err := json.Unmarshal(b, a); err != nil {
   515  		return nil, err
   516  	}
   517  	if len(a.GetNamespace()) == 0 {
   518  		a.SetNamespace("default")
   519  	}
   520  	return a, nil
   521  }
   522  
   523  // Object2Map turn the Object to a map
   524  func Object2Map(obj interface{}) (map[string]interface{}, error) {
   525  	var res map[string]interface{}
   526  	bts, err := json.Marshal(obj)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  	err = json.Unmarshal(bts, &res)
   531  	return res, err
   532  }
   533  
   534  // Object2RawExtension converts an object to a rawExtension
   535  func Object2RawExtension(obj interface{}) *runtime.RawExtension {
   536  	bts := MustJSONMarshal(obj)
   537  	return &runtime.RawExtension{
   538  		Raw: bts,
   539  	}
   540  }
   541  
   542  // MustJSONMarshal json-marshals an object into bytes. It panics on err.
   543  func MustJSONMarshal(obj interface{}) []byte {
   544  	b, err := json.Marshal(obj)
   545  	if err != nil {
   546  		panic(err)
   547  	}
   548  	return b
   549  }
   550  
   551  // RawExtension2Map will convert rawExtension to map
   552  func RawExtension2Map(raw *runtime.RawExtension) (map[string]interface{}, error) {
   553  	if raw == nil {
   554  		return nil, nil
   555  	}
   556  	data, err := raw.MarshalJSON()
   557  	if err != nil {
   558  		return nil, err
   559  	}
   560  	var ret map[string]interface{}
   561  	err = json.Unmarshal(data, &ret)
   562  	if err != nil {
   563  		return nil, err
   564  	}
   565  	return ret, err
   566  }
   567  
   568  // GenTraitName generate trait name
   569  func GenTraitName(componentName string, ct *unstructured.Unstructured, traitType string) string {
   570  	var traitMiddleName = TraitPrefixKey
   571  	if traitType != "" && traitType != Dummy {
   572  		traitMiddleName = strings.ToLower(traitType)
   573  	}
   574  	return fmt.Sprintf("%s-%s-%s", componentName, traitMiddleName, ComputeHash(ct))
   575  }
   576  
   577  // ComputeHash returns a hash value calculated from pod template and
   578  // a collisionCount to avoid hash collision. The hash will be safe encoded to
   579  // avoid bad words.
   580  func ComputeHash(trait *unstructured.Unstructured) string {
   581  	componentTraitHasher := fnv.New32a()
   582  	DeepHashObject(componentTraitHasher, *trait)
   583  
   584  	return rand.SafeEncodeString(fmt.Sprint(componentTraitHasher.Sum32()))
   585  }
   586  
   587  // DeepHashObject writes specified object to hash using the spew library
   588  // which follows pointers and prints actual values of the nested objects
   589  // ensuring the hash does not change when a pointer changes.
   590  func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
   591  	hasher.Reset()
   592  	printer := spew.ConfigState{
   593  		Indent:         " ",
   594  		SortKeys:       true,
   595  		DisableMethods: true,
   596  		SpewKeys:       true,
   597  	}
   598  	_, _ = printer.Fprintf(hasher, "%#v", objectToWrite)
   599  }
   600  
   601  // AddLabels will merge labels with existing labels. If any conflict keys, use new value to override existing value.
   602  func AddLabels(o labelAnnotationObject, labels map[string]string) {
   603  	o.SetLabels(MergeMapOverrideWithDst(o.GetLabels(), labels))
   604  }
   605  
   606  // AddAnnotations will merge annotations with existing ones. If any conflict keys, use new value to override existing value.
   607  func AddAnnotations(o labelAnnotationObject, annos map[string]string) {
   608  	o.SetAnnotations(MergeMapOverrideWithDst(o.GetAnnotations(), annos))
   609  }
   610  
   611  // MergeMapOverrideWithDst merges two could be nil maps. Keep the dst for any conflicts,
   612  func MergeMapOverrideWithDst(src, dst map[string]string) map[string]string {
   613  	if src == nil && dst == nil {
   614  		return nil
   615  	}
   616  	r := make(map[string]string)
   617  	for k, v := range src {
   618  		r[k] = v
   619  	}
   620  	// override the src for the same key
   621  	for k, v := range dst {
   622  		r[k] = v
   623  	}
   624  	return r
   625  }
   626  
   627  // ExtractComponentName will extract the componentName from a revisionName
   628  func ExtractComponentName(revisionName string) string {
   629  	splits := strings.Split(revisionName, "-")
   630  	return strings.Join(splits[0:len(splits)-1], "-")
   631  }
   632  
   633  // ExtractRevisionNum  extract revision number
   634  func ExtractRevisionNum(appRevision string, delimiter string) (int, error) {
   635  	splits := strings.Split(appRevision, delimiter)
   636  	// check some bad appRevision name, eg:v1, appv2
   637  	if len(splits) == 1 {
   638  		return 0, errors.New(ErrBadRevision)
   639  	}
   640  	// check some bad appRevision name, eg:myapp-a1
   641  	if !strings.HasPrefix(splits[len(splits)-1], "v") {
   642  		return 0, errors.New(ErrBadRevision)
   643  	}
   644  	return strconv.Atoi(strings.TrimPrefix(splits[len(splits)-1], "v"))
   645  }
   646  
   647  // Min for int
   648  func Min(a, b int) int {
   649  	if a < b {
   650  		return a
   651  	}
   652  	return b
   653  }
   654  
   655  // Max for int
   656  func Max(a, b int) int {
   657  	if a > b {
   658  		return a
   659  	}
   660  	return b
   661  }
   662  
   663  // Abs for int
   664  func Abs(a int) int {
   665  	if a < 0 {
   666  		return -a
   667  	}
   668  	return a
   669  }
   670  
   671  // AsOwner converts the supplied object reference to an owner reference.
   672  func AsOwner(r *corev1.ObjectReference) metav1.OwnerReference {
   673  	return metav1.OwnerReference{
   674  		APIVersion: r.APIVersion,
   675  		Kind:       r.Kind,
   676  		Name:       r.Name,
   677  		UID:        r.UID,
   678  	}
   679  }
   680  
   681  // AsController converts the supplied object reference to a controller
   682  // reference. You may also consider using metav1.NewControllerRef.
   683  func AsController(r *corev1.ObjectReference) metav1.OwnerReference {
   684  	c := true
   685  	ref := AsOwner(r)
   686  	ref.Controller = &c
   687  	return ref
   688  }
   689  
   690  // NamespaceAccessor namespace accessor for resource
   691  type NamespaceAccessor interface {
   692  	For(obj client.Object) string
   693  	Namespace() string
   694  }
   695  
   696  type applicationResourceNamespaceAccessor struct {
   697  	applicationNamespace string
   698  	overrideNamespace    string
   699  }
   700  
   701  // For access namespace for resource
   702  func (accessor *applicationResourceNamespaceAccessor) For(obj client.Object) string {
   703  	if accessor.overrideNamespace != "" {
   704  		return accessor.overrideNamespace
   705  	}
   706  	if originalNamespace := obj.GetNamespace(); originalNamespace != "" {
   707  		return originalNamespace
   708  	}
   709  	return accessor.applicationNamespace
   710  }
   711  
   712  // Namespace the namespace by default
   713  func (accessor *applicationResourceNamespaceAccessor) Namespace() string {
   714  	if accessor.overrideNamespace != "" {
   715  		return accessor.overrideNamespace
   716  	}
   717  	return accessor.applicationNamespace
   718  }
   719  
   720  // NewApplicationResourceNamespaceAccessor create namespace accessor for resource in application
   721  func NewApplicationResourceNamespaceAccessor(appNs, overrideNs string) NamespaceAccessor {
   722  	return &applicationResourceNamespaceAccessor{applicationNamespace: appNs, overrideNamespace: overrideNs}
   723  }