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  }