github.com/tilt-dev/tilt@v0.36.0/internal/k8s/label_test.go (about)

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