github.com/zoumo/helm@v2.5.0+incompatible/pkg/releaseutil/annotation.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     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 releaseutil
    18  
    19  import (
    20  	"bytes"
    21  	"log"
    22  
    23  	"k8s.io/apimachinery/pkg/api/meta"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/runtime/serializer/json"
    26  	"k8s.io/client-go/kubernetes/scheme"
    27  	app "k8s.io/client-go/pkg/api/v1"
    28  	apps "k8s.io/client-go/pkg/apis/apps/v1beta1"
    29  	batch "k8s.io/client-go/pkg/apis/batch/v1"
    30  	batchv2 "k8s.io/client-go/pkg/apis/batch/v2alpha1"
    31  	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
    32  )
    33  
    34  // AnnotationKey is annotation key of kubernetes object
    35  type AnnotationKey string
    36  
    37  const (
    38  	// DefaultPathKey is the key of chart logic path. Logic path splits components by
    39  	// slash. For example:
    40  	// A chart like:
    41  	// rootchart --> subchart1
    42  	//           |-> subchart2
    43  	// The logic path of every charts:
    44  	// rootchart: rootchart
    45  	// subchart1: rootchart/subchart1
    46  	// subchart2: rootchart/subchart2
    47  	// If a kubernetes resource object have an annotation key named "helm.sh/path", Then
    48  	// its value should meet the requirements.
    49  	DefaultPathKey AnnotationKey = "helm.sh/path"
    50  	// defaultNamespaceKey is the key of release namespace
    51  	DefaultNamespaceKey AnnotationKey = "helm.sh/namespace"
    52  	// defaultReleaseKey is the key of release name
    53  	DefaultReleaseKey AnnotationKey = "helm.sh/release"
    54  )
    55  
    56  var (
    57  	// defaultSerializer is a codec and used for encoding and decoding kubernetes resources
    58  	defaultSerializer *json.Serializer = nil
    59  )
    60  
    61  func init() {
    62  	// create default serializer
    63  	defaultSerializer = json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme)
    64  }
    65  
    66  // InjectAnnotations adds key-value pairs to resource annotations. If resource is not a valid kubernetes
    67  // resource, it does nothing and returns original resource.
    68  func InjectAnnotations(resource string, annos map[AnnotationKey]string) string {
    69  	if len(annos) <= 0 {
    70  		return resource
    71  	}
    72  
    73  	// decode object
    74  	obj, _, err := defaultSerializer.Decode([]byte(resource), nil, nil)
    75  	if err != nil {
    76  		return resource
    77  	}
    78  	accessor := meta.NewAccessor()
    79  	annotations, err := accessor.Annotations(obj)
    80  	if err != nil {
    81  		return resource
    82  	}
    83  	err = accessor.SetAnnotations(obj, merge(annotations, annos))
    84  	if err != nil {
    85  		return resource
    86  	}
    87  
    88  	// check and add annotations to the template of specific types
    89  	switch ins := obj.(type) {
    90  	case *extensions.Deployment:
    91  		{
    92  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
    93  		}
    94  	case *apps.Deployment:
    95  		{
    96  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
    97  		}
    98  	case *extensions.DaemonSet:
    99  		{
   100  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
   101  		}
   102  	case *extensions.ReplicaSet:
   103  		{
   104  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
   105  		}
   106  	case *apps.StatefulSet:
   107  		{
   108  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
   109  		}
   110  	case *batch.Job:
   111  		{
   112  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
   113  		}
   114  	case *batchv2.CronJob:
   115  		{
   116  			ins.Spec.JobTemplate.Annotations = merge(ins.Spec.JobTemplate.Annotations, annos)
   117  			ins.Spec.JobTemplate.Spec.Template.Annotations = merge(ins.Spec.JobTemplate.Spec.Template.Annotations, annos)
   118  		}
   119  	case *app.ReplicationController:
   120  		{
   121  			ins.Spec.Template.Annotations = merge(ins.Spec.Template.Annotations, annos)
   122  		}
   123  	}
   124  
   125  	// encode object
   126  	buf := bytes.NewBuffer(nil)
   127  	err = defaultSerializer.Encode(obj, buf)
   128  	if err != nil {
   129  		return resource
   130  	}
   131  	return buf.String()
   132  }
   133  
   134  // merge merges annotations into origin
   135  func merge(origin map[string]string, annos map[AnnotationKey]string) map[string]string {
   136  	if origin == nil {
   137  		origin = make(map[string]string)
   138  	}
   139  	for k, v := range annos {
   140  		origin[string(k)] = v
   141  	}
   142  	return origin
   143  }
   144  
   145  // MatchReleaseByString matches two object string and check if they are from same release
   146  func MatchReleaseByString(a, b string) bool {
   147  	// decode object
   148  	objA, _, err := defaultSerializer.Decode([]byte(a), nil, nil)
   149  	if err != nil {
   150  		log.Printf("match: Failed to decode resource: %s", err)
   151  		return false
   152  	}
   153  	objB, _, err := defaultSerializer.Decode([]byte(b), nil, nil)
   154  	if err != nil {
   155  		log.Printf("match: Failed to decode resource: %s", err)
   156  		return false
   157  	}
   158  	return MatchRelease(objA, objB)
   159  }
   160  
   161  // MatchRelease matches two object and check if they are from same release
   162  func MatchRelease(a, b runtime.Object) bool {
   163  	accessor := meta.NewAccessor()
   164  	annoA, err := accessor.Annotations(a)
   165  	if err != nil {
   166  		annoA = map[string]string{}
   167  	}
   168  	annoB, err := accessor.Annotations(b)
   169  	if err != nil {
   170  		annoB = map[string]string{}
   171  	}
   172  	return matchReleaseAnnotations(annoA, annoB)
   173  }
   174  
   175  // matchReleaseAnnotations checks path, namespace and release
   176  func matchReleaseAnnotations(a, b map[string]string) bool {
   177  	keys := []string{string(DefaultPathKey), string(DefaultNamespaceKey), string(DefaultReleaseKey)}
   178  	for _, key := range keys {
   179  		v1, ok1 := a[key]
   180  		v2, ok2 := b[key]
   181  		if !(ok1 || ok2) {
   182  			continue
   183  		}
   184  		if v1 != v2 {
   185  			return false
   186  		}
   187  	}
   188  	return true
   189  }