github.com/grahambrereton-form3/tilt@v0.10.18/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  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/labels"
    12  
    13  	"github.com/windmilleng/tilt/pkg/model"
    14  )
    15  
    16  func makeLabelSet(lps []model.LabelPair) labels.Set {
    17  	ls := labels.Set{}
    18  	for _, lp := range lps {
    19  		ls[lp.Key] = lp.Value
    20  	}
    21  	return ls
    22  }
    23  
    24  func makeLabelSelector(lps []model.LabelPair) string {
    25  	return labels.SelectorFromSet(makeLabelSet(lps)).String()
    26  }
    27  
    28  func InjectLabels(entity K8sEntity, labels []model.LabelPair) (K8sEntity, error) {
    29  	return injectLabels(entity, labels, false)
    30  }
    31  
    32  func OverwriteLabels(entity K8sEntity, labels []model.LabelPair) (K8sEntity, error) {
    33  	return injectLabels(entity, labels, true)
    34  }
    35  
    36  func applyLabelsToMap(orig *map[string]string, labels []model.LabelPair, overwrite bool) {
    37  	if overwrite {
    38  		*orig = nil
    39  	}
    40  	for _, label := range labels {
    41  		if *orig == nil {
    42  			*orig = make(map[string]string, 1)
    43  		}
    44  		(*orig)[label.Key] = label.Value
    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  	switch obj := entity.Obj.(type) {
    54  	case *appsv1beta1.Deployment:
    55  		allowLabelChangesInAppsDeploymentBeta1(obj)
    56  	case *appsv1beta2.Deployment:
    57  		allowLabelChangesInAppsDeploymentBeta2(obj)
    58  	case *extv1beta1.Deployment:
    59  		allowLabelChangesInExtDeploymentBeta1(obj)
    60  	}
    61  
    62  	// Don't modify persistent volume claims
    63  	// because they're supposed to be immutable.
    64  	pvc := reflect.TypeOf(v1.PersistentVolumeClaim{})
    65  	metas, err := extractObjectMetas(&entity, func(v reflect.Value) bool {
    66  		return v.Type() != pvc
    67  	})
    68  	if err != nil {
    69  		return K8sEntity{}, err
    70  	}
    71  
    72  	for _, meta := range metas {
    73  		applyLabelsToMap(&meta.Labels, labels, overwrite)
    74  	}
    75  
    76  	selectors, err := extractSelectors(&entity, func(v reflect.Value) bool {
    77  		return v.Type() != pvc
    78  	})
    79  	for _, selector := range selectors {
    80  		applyLabelsToMap(&selector.MatchLabels, labels, overwrite)
    81  	}
    82  	return entity, nil
    83  }
    84  
    85  // In the v1beta1 API, if a Deployment didn't have a selector,
    86  // Kubernetes would automatically infer a selector based on the labels
    87  // in the pod.
    88  //
    89  // The problem is that Selectors are immutable. So this had the unintended
    90  // side-effect of making pod labels immutable. Since many tools attach
    91  // arbitrary labels to pods, v1beta1 Deployments broke lots of tools.
    92  //
    93  // The v1 Deployment fixed this problem by making Selector mandatory.
    94  // But for old versions of Deployment, we need to auto-infer the selector
    95  // before we add labels to the pod.
    96  func allowLabelChangesInAppsDeploymentBeta1(dep *appsv1beta1.Deployment) {
    97  	selector := dep.Spec.Selector
    98  	if selector != nil &&
    99  		(len(selector.MatchLabels) > 0 || len(selector.MatchExpressions) > 0) {
   100  		return
   101  	}
   102  
   103  	podSpecLabels := dep.Spec.Template.Labels
   104  	matchLabels := make(map[string]string, len(podSpecLabels))
   105  	for k, v := range podSpecLabels {
   106  		matchLabels[k] = v
   107  	}
   108  	if dep.Spec.Selector == nil {
   109  		dep.Spec.Selector = &metav1.LabelSelector{}
   110  	}
   111  	dep.Spec.Selector.MatchLabels = matchLabels
   112  }
   113  
   114  // see notes on allowLabelChangesInAppsDeploymentBeta1
   115  func allowLabelChangesInAppsDeploymentBeta2(dep *appsv1beta2.Deployment) {
   116  	selector := dep.Spec.Selector
   117  	if selector != nil &&
   118  		(len(selector.MatchLabels) > 0 || len(selector.MatchExpressions) > 0) {
   119  		return
   120  	}
   121  
   122  	podSpecLabels := dep.Spec.Template.Labels
   123  	matchLabels := make(map[string]string, len(podSpecLabels))
   124  	for k, v := range podSpecLabels {
   125  		matchLabels[k] = v
   126  	}
   127  	if dep.Spec.Selector == nil {
   128  		dep.Spec.Selector = &metav1.LabelSelector{}
   129  	}
   130  	dep.Spec.Selector.MatchLabels = matchLabels
   131  }
   132  
   133  func allowLabelChangesInExtDeploymentBeta1(dep *extv1beta1.Deployment) {
   134  	selector := dep.Spec.Selector
   135  	if selector != nil &&
   136  		(len(selector.MatchLabels) > 0 || len(selector.MatchExpressions) > 0) {
   137  		return
   138  	}
   139  
   140  	podSpecLabels := dep.Spec.Template.Labels
   141  	matchLabels := make(map[string]string, len(podSpecLabels))
   142  	for k, v := range podSpecLabels {
   143  		matchLabels[k] = v
   144  	}
   145  	if dep.Spec.Selector == nil {
   146  		dep.Spec.Selector = &metav1.LabelSelector{}
   147  	}
   148  	dep.Spec.Selector.MatchLabels = matchLabels
   149  }
   150  
   151  // SelectorMatchesLabels indicates whether the pod selector of the given entity matches the given label(s).
   152  // Currently only supports Services, but may be expanded to support other types that
   153  // match pods via selectors.
   154  func (e K8sEntity) SelectorMatchesLabels(labels map[string]string) bool {
   155  	svc, ok := e.Obj.(*v1.Service)
   156  	if !ok {
   157  		return false
   158  	}
   159  	selector := svc.Spec.Selector
   160  	for k, selVal := range selector {
   161  		realVal, ok := labels[k]
   162  		if !ok || realVal != selVal {
   163  			return false
   164  		}
   165  	}
   166  	return true
   167  
   168  }
   169  
   170  // MatchesMetadataLabels indicates whether the given label(s) are a subset
   171  // of metadata labels for the given entity.
   172  func (e K8sEntity) MatchesMetadataLabels(labels map[string]string) (bool, error) {
   173  	// TODO(nick): This doesn't seem right.
   174  	// This should only look at the top-level obj meta, not all of them.
   175  	metas, err := extractObjectMetas(&e, NoFilter)
   176  	if err != nil {
   177  		return false, err
   178  	}
   179  
   180  	for _, meta := range metas {
   181  		for k, v := range labels {
   182  			realVal, ok := meta.Labels[k]
   183  			if !ok || realVal != v {
   184  				return false, nil
   185  			}
   186  		}
   187  	}
   188  
   189  	return true, nil
   190  
   191  }