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 }