github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/label_test.go (about) 1 package k8s 2 3 import ( 4 "testing" 5 6 extbeta1 "k8s.io/api/extensions/v1beta1" 7 8 "k8s.io/api/apps/v1beta1" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 appsv1 "k8s.io/api/apps/v1" 13 "k8s.io/api/apps/v1beta2" 14 v1 "k8s.io/api/core/v1" 15 16 "github.com/tilt-dev/tilt/internal/k8s/testyaml" 17 "github.com/tilt-dev/tilt/pkg/model" 18 ) 19 20 type field struct { 21 name string 22 m map[string]string 23 } 24 25 func verifyFields(t *testing.T, expected []model.LabelPair, fields []field) { 26 em := make(map[string]string) 27 for _, l := range expected { 28 em[l.Key] = l.Value 29 } 30 31 for _, f := range fields { 32 require.Equal(t, em, f.m, f.name) 33 } 34 } 35 36 func TestInjectLabelPod(t *testing.T) { 37 entity := parseOneEntity(t, testyaml.LonelyPodYAML) 38 lps := []model.LabelPair{ 39 { 40 Key: "tier", 41 Value: "test", 42 }, 43 } 44 newEntity, err := InjectLabels(entity, lps) 45 if err != nil { 46 t.Fatal(err) 47 } 48 49 p, ok := newEntity.Obj.(*v1.Pod) 50 require.True(t, ok) 51 52 verifyFields(t, lps, []field{{"pod.Labels", p.Labels}}) 53 } 54 55 func TestInjectLabelDeployment(t *testing.T) { 56 entity := parseOneEntity(t, testyaml.SanchoYAML) 57 lps := []model.LabelPair{ 58 {Key: "tier", Value: "test"}, 59 {Key: "owner", Value: "me"}, 60 } 61 newEntity, err := InjectLabels(entity, lps) 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 d, ok := newEntity.Obj.(*appsv1.Deployment) 67 require.True(t, ok) 68 69 appLP := model.LabelPair{Key: "app", Value: "sancho"} 70 expectedLPs := append(lps, appLP) 71 72 verifyFields(t, expectedLPs, []field{ 73 {"d.Labels", d.Labels}, 74 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 75 }) 76 // matchlabels is not updated 77 verifyFields(t, []model.LabelPair{appLP}, []field{ 78 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 79 }) 80 } 81 82 func TestInjectLabelDeploymentMakeSelectorMatchOnConflict(t *testing.T) { 83 entity := parseOneEntity(t, testyaml.SanchoYAML) 84 lps := []model.LabelPair{ 85 { 86 Key: "app", 87 Value: "panza", 88 }, 89 } 90 newEntity, err := InjectLabels(entity, lps) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 d, ok := newEntity.Obj.(*appsv1.Deployment) 96 require.True(t, ok) 97 98 verifyFields(t, lps, []field{ 99 {"d.Labels", d.Labels}, 100 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 101 }) 102 // matchlabels only gets its existing 'app' label updated, it doesn't get any new labels added 103 verifyFields(t, []model.LabelPair{{Key: "app", Value: "panza"}}, []field{ 104 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 105 }) 106 } 107 108 func TestInjectLabelDeploymentBeta1(t *testing.T) { 109 entity := parseOneEntity(t, testyaml.SanchoBeta1YAML) 110 lps := []model.LabelPair{ 111 { 112 Key: "owner", 113 Value: "me", 114 }, 115 } 116 newEntity, err := InjectLabels(entity, lps) 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 d, ok := newEntity.Obj.(*v1beta1.Deployment) 122 require.True(t, ok) 123 124 expectedLPs := append(lps, model.LabelPair{Key: "app", Value: "sancho"}) 125 126 verifyFields(t, expectedLPs, []field{ 127 {"d.Labels", d.Labels}, 128 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 129 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 130 }) 131 } 132 133 func TestInjectLabelStatefulSetBeta1(t *testing.T) { 134 entity := parseOneEntity(t, testyaml.SanchoStatefulSetBeta1YAML) 135 lps := []model.LabelPair{ 136 { 137 Key: "owner", 138 Value: "me", 139 }, 140 } 141 newEntity, err := InjectLabels(entity, lps) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 d, ok := newEntity.Obj.(*v1beta1.StatefulSet) 147 require.True(t, ok) 148 149 expectedLPs := append(lps, model.LabelPair{Key: "app", Value: "sancho"}) 150 151 verifyFields(t, expectedLPs, []field{ 152 {"d.Labels", d.Labels}, 153 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 154 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 155 }) 156 } 157 158 func TestInjectLabelDeploymentBeta2(t *testing.T) { 159 entity := parseOneEntity(t, testyaml.SanchoBeta2YAML) 160 lps := []model.LabelPair{ 161 { 162 Key: "owner", 163 Value: "me", 164 }, 165 } 166 newEntity, err := InjectLabels(entity, lps) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 d, ok := newEntity.Obj.(*v1beta2.Deployment) 172 require.True(t, ok) 173 174 expectedLPs := append(lps, model.LabelPair{Key: "app", Value: "sancho"}) 175 176 verifyFields(t, expectedLPs, []field{ 177 {"d.Labels", d.Labels}, 178 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 179 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 180 }) 181 } 182 183 func TestInjectLabelExtDeploymentBeta1(t *testing.T) { 184 entity := parseOneEntity(t, testyaml.SanchoExtBeta1YAML) 185 lps := []model.LabelPair{ 186 { 187 Key: "owner", 188 Value: "me", 189 }, 190 } 191 newEntity, err := InjectLabels(entity, lps) 192 if err != nil { 193 t.Fatal(err) 194 } 195 196 d, ok := newEntity.Obj.(*extbeta1.Deployment) 197 require.True(t, ok) 198 199 expectedLPs := append(lps, model.LabelPair{Key: "app", Value: "sancho"}) 200 201 verifyFields(t, expectedLPs, []field{ 202 {"d.Labels", d.Labels}, 203 {"d.Spec.Template.Labels", d.Spec.Template.Labels}, 204 {"d.Spec.Selector.MatchLabels", d.Spec.Selector.MatchLabels}, 205 }) 206 } 207 208 func TestInjectStatefulSet(t *testing.T) { 209 entity := parseOneEntity(t, testyaml.RedisStatefulSetYAML) 210 lps := []model.LabelPair{ 211 { 212 Key: "tilt-runid", 213 Value: "deadbeef", 214 }, 215 } 216 newEntity, err := InjectLabels(entity, lps) 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 expectedLPs := append(lps, []model.LabelPair{ 222 {Key: "app", Value: "redis"}, 223 {Key: "chart", Value: "redis-5.1.3"}, 224 {Key: "release", Value: "test"}, 225 }...) 226 227 ss := newEntity.Obj.(*v1beta2.StatefulSet) 228 verifyFields(t, append(expectedLPs, model.LabelPair{Key: "heritage", Value: "Tiller"}), []field{ 229 {"ss.Labels", ss.Labels}, 230 }) 231 verifyFields(t, append(expectedLPs, model.LabelPair{Key: "role", Value: "master"}), []field{ 232 {"ss.Spec.Template.Labels", ss.Spec.Template.Labels}, 233 }) 234 verifyFields(t, 235 []model.LabelPair{ 236 {Key: "app", Value: "redis"}, 237 {Key: "release", Value: "test"}, 238 {Key: "role", Value: "master"}, 239 }, []field{ 240 {"ss.Spec.Selector.MatchLabels", ss.Spec.Selector.MatchLabels}, 241 }) 242 243 verifyFields(t, 244 []model.LabelPair{ 245 {Key: "app", Value: "redis"}, 246 {Key: "component", Value: "master"}, 247 {Key: "heritage", Value: "Tiller"}, 248 {Key: "release", Value: "test"}, 249 }, []field{ 250 {"ss.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels", ss.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels}, 251 }) 252 } 253 254 func TestInjectService(t *testing.T) { 255 entity := parseOneEntity(t, testyaml.DoggosServiceYaml) 256 lps := []model.LabelPair{ 257 {Key: "foo", Value: "bar"}, 258 {Key: "app", Value: "cattos"}, 259 } 260 newEntity, err := InjectLabels(entity, lps) 261 require.NoError(t, err) 262 263 svc, ok := newEntity.Obj.(*v1.Service) 264 require.True(t, ok) 265 266 expectedLPs := append(lps, model.LabelPair{Key: "whosAGoodBoy", Value: "imAGoodBoy"}) 267 verifyFields(t, expectedLPs, []field{ 268 {"svc.Labels", svc.Labels}, 269 }) 270 271 // selector only gets existing labels updated 272 verifyFields(t, []model.LabelPair{{Key: "app", Value: "cattos"}}, []field{ 273 {"svc.Spec.Selector", svc.Spec.Selector}, 274 }) 275 } 276 277 func TestSelectorMatchesLabels(t *testing.T) { 278 entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML) 279 if err != nil { 280 t.Fatal(err) 281 } 282 if len(entities) != 2 { 283 t.Fatal("expected exactly two entities") 284 } 285 if entities[0].GVK().Kind != "Service" { 286 t.Fatal("expected first entity to be a Service") 287 } 288 if entities[1].GVK().Kind != "Deployment" { 289 t.Fatal("expected second entity to be a Deployment") 290 } 291 292 svc := entities[0] 293 dep := entities[1] 294 295 labels := map[string]string{ 296 "app": "blorg", 297 "owner": "nick", 298 "environment": "devel", 299 "tier": "backend", 300 "foo": "bar", // an extra label on the pod shouldn't affect the match 301 } 302 303 assert.True(t, svc.SelectorMatchesLabels(labels)) 304 305 assert.False(t, dep.SelectorMatchesLabels(labels), "kind Deployment does not support SelectorMatchesLabels") 306 307 labels["app"] = "not-blorg" 308 assert.False(t, svc.SelectorMatchesLabels(labels), "wrong value for an expected key") 309 310 delete(labels, "app") 311 assert.False(t, svc.SelectorMatchesLabels(labels), "expected key missing") 312 313 service, ok := svc.Obj.(*v1.Service) 314 require.True(t, ok, "typing svc as k8s Service") 315 service.Spec.Selector = nil 316 assert.False(t, svc.SelectorMatchesLabels(labels), "empty selector should match nothing") 317 } 318 319 func TestMatchesMetadataLabels(t *testing.T) { 320 entities, err := ParseYAMLFromString(testyaml.DoggosServiceYaml) 321 if err != nil { 322 t.Fatal(err) 323 } 324 if len(entities) != 1 { 325 t.Fatal("expected exactly two entities") 326 } 327 e := entities[0] 328 329 exactMatch := map[string]string{ 330 "app": "doggos", 331 "whosAGoodBoy": "imAGoodBoy", 332 } 333 assertMatchesMetadataLabels(t, e, exactMatch, true, "same set of labels should match") 334 335 subset := map[string]string{ 336 "app": "doggos", 337 } 338 assertMatchesMetadataLabels(t, e, subset, true, "subset of labels should match") 339 340 labelsWithExtra := map[string]string{ 341 "app": "doggos", 342 "whosAGoodBoy": "imAGoodBoy", 343 "tooManyLabels": "yep", 344 } 345 assertMatchesMetadataLabels(t, e, labelsWithExtra, false, "extra key not in metadata") 346 347 wrongValForKey := map[string]string{ 348 "app": "doggos", 349 "whosAGoodBoy": "notMeWhoops", 350 } 351 assertMatchesMetadataLabels(t, e, wrongValForKey, false, "label with wrong val for key") 352 } 353 354 func assertMatchesMetadataLabels(t *testing.T, e K8sEntity, labels map[string]string, expected bool, msg string) { 355 match, err := e.MatchesMetadataLabels(labels) 356 if err != nil { 357 t.Errorf("error checking if entity %s matches labels %v: %v", e.Name(), labels, err) 358 } 359 assert.Equal(t, expected, match, "expected entity %s matches metadata labels %v --> %t (%s)", 360 e.Name(), labels, expected, msg) 361 } 362 func parseOneEntity(t *testing.T, yaml string) K8sEntity { 363 entities, err := ParseYAMLFromString(yaml) 364 if err != nil { 365 t.Fatal(err) 366 } 367 368 if len(entities) != 1 { 369 t.Fatalf("Unexpected entities: %+v", entities) 370 } 371 return entities[0] 372 }