github.com/tilt-dev/tilt@v0.36.0/internal/k8s/label.go (about)

     1  package k8s
     2  
     3  import (
     4  	"reflect"
     5  
     6  	appsv1beta1 "k8s.io/api/apps/v1beta1"
     7  	appsv1beta2 "k8s.io/api/apps/v1beta2"
     8  	v1 "k8s.io/api/core/v1"
     9  	extv1beta1 "k8s.io/api/extensions/v1beta1"
    10  	"k8s.io/apimachinery/pkg/api/meta"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  
    14  	"github.com/tilt-dev/tilt/pkg/model"
    15  )
    16  
    17  func InjectLabels(entity K8sEntity, labels []model.LabelPair) (K8sEntity, error) {
    18  	return injectLabels(entity, labels, false)
    19  }
    20  
    21  func OverwriteLabels(entity K8sEntity, labels []model.LabelPair) (K8sEntity, error) {
    22  	return injectLabels(entity, labels, true)
    23  }
    24  
    25  // labels: labels to be added to `dest`
    26  // overwrite: if true, merge `labels` into `dest`. if false, replace `dest` with `labels`
    27  // addNew: if true, add all `labels` to `dest`. if false, only add `labels` whose keys are already in `dest`
    28  func applyLabelsToMap(dest *map[string]string, labels []model.LabelPair, overwrite bool, addNew bool) {
    29  	orig := *dest
    30  	if overwrite {
    31  		*dest = nil
    32  	}
    33  	for _, label := range labels {
    34  		if *dest == nil {
    35  			*dest = make(map[string]string, 1)
    36  		}
    37  		preexisting := false
    38  		if orig != nil {
    39  			_, preexisting = orig[label.Key]
    40  		}
    41  
    42  		if addNew || preexisting {
    43  			(*dest)[label.Key] = label.Value
    44  		}
    45  	}
    46  }
    47  
    48  // injectLabels injects the given labels into the given k8sEntity
    49  // (if `overwrite`, replacing existing labels)
    50  func injectLabels(entity K8sEntity, labels []model.LabelPair, overwrite bool) (K8sEntity, error) {
    51  	entity = entity.DeepCopy()
    52  
    53  	// Don't modify persistent volume claims
    54  	// because they're supposed to be immutable.
    55  	pvc := reflect.TypeOf(v1.PersistentVolumeClaim{})
    56  	metas, err := extractObjectMetas(entity.Obj, func(v reflect.Value) bool {
    57  		return v.Type() != pvc
    58  	})
    59  	if err != nil {
    60  		return K8sEntity{}, err
    61  	}
    62  
    63  	for _, meta := range metas {
    64  		metaLabels := meta.GetLabels()
    65  		if metaLabels == nil {
    66  			metaLabels = make(map[string]string, 0)
    67  		}
    68  		applyLabelsToMap(&metaLabels, labels, overwrite, true)
    69  		meta.SetLabels(metaLabels)
    70  	}
    71  
    72  	switch obj := entity.Obj.(type) {
    73  	case *appsv1beta1.Deployment,
    74  		*appsv1beta2.Deployment,
    75  		*extv1beta1.Deployment,
    76  		*appsv1beta2.ReplicaSet,
    77  		*extv1beta1.ReplicaSet,
    78  		*appsv1beta2.DaemonSet,
    79  		*extv1beta1.DaemonSet,
    80  		*appsv1beta1.StatefulSet,
    81  		*appsv1beta2.StatefulSet:
    82  		allowLabelChangesInOptionalSelector(obj)
    83  	}
    84  
    85  	selectors, err := extractSelectors(&entity, func(v reflect.Value) bool {
    86  		return v.Type() != pvc
    87  	})
    88  	if err != nil {
    89  		return K8sEntity{}, err
    90  	}
    91  	for _, selector := range selectors {
    92  		applyLabelsToMap(&selector.MatchLabels, labels, overwrite, false)
    93  	}
    94  
    95  	// ServiceSpecs have a map[string]string instead of a LabelSelector, so handle them specially
    96  	serviceSpecs, err := extractServiceSpecs(&entity)
    97  	if err != nil {
    98  		return K8sEntity{}, err
    99  	}
   100  	for _, s := range serviceSpecs {
   101  		applyLabelsToMap(&s.Selector, labels, overwrite, false)
   102  	}
   103  	return entity, nil
   104  }
   105  
   106  // In the v1beta1 API, if a Deployment didn't have a selector,
   107  // Kubernetes would automatically infer a selector based on the labels
   108  // in the pod.
   109  //
   110  // The problem is that Selectors are immutable. So this had the unintended
   111  // side-effect of making pod labels immutable. Since many tools attach
   112  // arbitrary labels to pods, v1beta1 Deployments broke lots of tools.
   113  //
   114  // The v1 Deployment fixed this problem by making Selector mandatory.
   115  // But for old versions of Deployment, we need to auto-infer the selector
   116  // before we add labels to the pod.
   117  func allowLabelChangesInOptionalSelector(obj runtime.Object) {
   118  	// First, check to make sure this has a Spec object with:
   119  	// 1) A Selector field of type metav1.LabelSelector
   120  	// 2) A Template field of type v1.PodTemplateSpec
   121  	objPtrV := reflect.ValueOf(obj)
   122  	if objPtrV.Kind() != reflect.Ptr {
   123  		return
   124  	}
   125  
   126  	objV := objPtrV.Elem()
   127  	if objV.Kind() != reflect.Struct {
   128  		return
   129  	}
   130  
   131  	specField := objV.FieldByName("Spec")
   132  	if specField.Kind() != reflect.Struct {
   133  		return
   134  	}
   135  
   136  	specType := specField.Type()
   137  	selectorFieldType, ok := specType.FieldByName("Selector")
   138  	if !ok || selectorFieldType.Type != reflect.TypeOf(&metav1.LabelSelector{}) {
   139  		return
   140  	}
   141  
   142  	podTemplateFieldType, ok := specType.FieldByName("Template")
   143  	if !ok || podTemplateFieldType.Type != reflect.TypeOf(v1.PodTemplateSpec{}) {
   144  		return
   145  	}
   146  
   147  	selectorField := specField.FieldByName("Selector")
   148  	var selector *metav1.LabelSelector
   149  	if selectorField.IsValid() {
   150  		selector, ok = selectorField.Interface().(*metav1.LabelSelector)
   151  		if !ok {
   152  			return
   153  		}
   154  	}
   155  
   156  	// If the selector is already filled in, we don't need to do anything.
   157  	if selector != nil &&
   158  		(len(selector.MatchLabels) > 0 || len(selector.MatchExpressions) > 0) {
   159  		return
   160  	}
   161  
   162  	podTemplateField := specField.FieldByName("Template")
   163  	podTemplate, ok := podTemplateField.Interface().(v1.PodTemplateSpec)
   164  	if !ok {
   165  		return
   166  	}
   167  
   168  	podSpecLabels := podTemplate.Labels
   169  	matchLabels := make(map[string]string, len(podSpecLabels))
   170  	for k, v := range podSpecLabels {
   171  		matchLabels[k] = v
   172  	}
   173  
   174  	if selector == nil {
   175  		selector = &metav1.LabelSelector{}
   176  	}
   177  	selector.MatchLabels = matchLabels
   178  	selectorField.Set(reflect.ValueOf(selector))
   179  }
   180  
   181  // SelectorMatchesLabels indicates whether the pod selector of the given entity
   182  // matches the given label(s). For an empty selector, returns `false`.
   183  // Currently only supports Services, but may be expanded to support other types that
   184  // match pods via selectors.
   185  func (e K8sEntity) SelectorMatchesLabels(labels map[string]string) bool {
   186  	svc, ok := e.Obj.(*v1.Service)
   187  	if !ok {
   188  		return false
   189  	}
   190  	selector := svc.Spec.Selector
   191  
   192  	if len(selector) == 0 {
   193  		return false
   194  	}
   195  
   196  	for k, selVal := range selector {
   197  		realVal, ok := labels[k]
   198  		if !ok || realVal != selVal {
   199  			return false
   200  		}
   201  	}
   202  	return true
   203  
   204  }
   205  
   206  // MatchesMetadataLabels indicates whether the given label(s) are a subset
   207  // of metadata labels for the given entity.
   208  func (e K8sEntity) MatchesMetadataLabels(labels map[string]string) (bool, error) {
   209  	meta, err := meta.Accessor(e.Obj)
   210  	if err != nil {
   211  		return false, err
   212  	}
   213  
   214  	metaLabels := meta.GetLabels()
   215  	for k, v := range labels {
   216  		realVal, ok := metaLabels[k]
   217  		if !ok || realVal != v {
   218  			return false, nil
   219  		}
   220  	}
   221  
   222  	return true, nil
   223  }