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 }