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 }