github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/entity_test.go (about)

     1  package k8s
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	v1 "k8s.io/api/core/v1"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  	"k8s.io/apimachinery/pkg/runtime/schema"
    13  
    14  	"github.com/windmilleng/tilt/internal/k8s/testyaml"
    15  	"github.com/windmilleng/tilt/internal/kustomize"
    16  )
    17  
    18  func TestTypedPodGVK(t *testing.T) {
    19  	entity := NewK8sEntity(&v1.Pod{})
    20  	assert.Equal(t, "", entity.GVK().Group)
    21  	assert.Equal(t, "v1", entity.GVK().Version)
    22  	assert.Equal(t, "Pod", entity.GVK().Kind)
    23  }
    24  
    25  func TestTypedDeploymentGVK(t *testing.T) {
    26  	entity := NewK8sEntity(&appsv1.Deployment{})
    27  	assert.Equal(t, "apps", entity.GVK().Group)
    28  	assert.Equal(t, "v1", entity.GVK().Version)
    29  	assert.Equal(t, "Deployment", entity.GVK().Kind)
    30  }
    31  
    32  func TestName(t *testing.T) {
    33  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  
    38  	assert.Equal(t, 2, len(entities))
    39  	assert.Equal(t, "devel-nick-lb-blorg-be", entities[0].Name())
    40  	assert.Equal(t, "devel-nick-blorg-be", entities[1].Name())
    41  }
    42  
    43  func TestNamespace(t *testing.T) {
    44  	entities, err := ParseYAMLFromString(testyaml.SyncletYAML)
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	assert.Equal(t, 1, len(entities))
    50  	assert.Equal(t, "kube-system", string(entities[0].Namespace()))
    51  }
    52  
    53  func TestImmutableFilter(t *testing.T) {
    54  	yaml := fmt.Sprintf("%s\n---\n%s\n---\n%s", testyaml.JobYAML, testyaml.SanchoYAML, testyaml.PodYAML)
    55  	entities, err := ParseYAMLFromString(yaml)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	immEntities := ImmutableEntities(entities)
    61  	if len(immEntities) != 2 {
    62  		t.Fatalf("Expected 2 entities, actual: %d", len(immEntities))
    63  	}
    64  
    65  	if immEntities[0].GVK().Kind != "Job" {
    66  		t.Errorf("Expected Job entity, actual: %+v", immEntities)
    67  	}
    68  	if immEntities[1].GVK().Kind != "Pod" {
    69  		t.Errorf("Expected Pod entity, actual: %+v", immEntities)
    70  	}
    71  }
    72  
    73  func TestMutableFilter(t *testing.T) {
    74  	yaml := fmt.Sprintf("%s\n---\n%s", testyaml.JobYAML, testyaml.SanchoYAML)
    75  	entities, err := ParseYAMLFromString(yaml)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	results := MutableEntities(entities)
    81  	if len(results) != 1 {
    82  		t.Fatalf("Expected 1 entity, actual: %d", len(results))
    83  	}
    84  
    85  	if results[0].GVK().Kind != "Deployment" {
    86  		t.Errorf("Expected Deployment entity, actual: %+v", results)
    87  	}
    88  }
    89  
    90  func TestLoadBalancerSpecs(t *testing.T) {
    91  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	lbs := ToLoadBalancerSpecs(entities)
    97  	if len(lbs) != 1 {
    98  		t.Fatalf("Expected 1 loadbalancer, actual %d: %v", len(lbs), lbs)
    99  	}
   100  
   101  	if lbs[0].Name != "devel-nick-lb-blorg-be" ||
   102  		lbs[0].Ports[0] != 8080 {
   103  		t.Fatalf("Unexpected loadbalancer: %+v", lbs[0])
   104  	}
   105  }
   106  
   107  func TestFilter(t *testing.T) {
   108  	entities, err := parseYAMLFromStrings(testyaml.BlorgBackendYAML, testyaml.BlorgJobYAML)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	test := func(e K8sEntity) (bool, error) {
   114  		if e.GVK().Kind == "Deployment" || e.GVK().Kind == "Job" {
   115  			return true, nil
   116  		}
   117  		return false, nil
   118  	}
   119  
   120  	popped, rest, err := Filter(entities, test)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	expectedPopped := []K8sEntity{entities[1], entities[2]} // deployment, job
   125  	expectedRest := []K8sEntity{entities[0]}                // service
   126  	assert.Equal(t, popped, expectedPopped)
   127  	assert.Equal(t, rest, expectedRest)
   128  
   129  	returnFalse := func(e K8sEntity) (bool, error) { return false, nil }
   130  	popped, rest, err = Filter(entities, returnFalse)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	assert.Empty(t, popped)
   135  	assert.Equal(t, rest, entities)
   136  
   137  	returnErr := func(e K8sEntity) (bool, error) {
   138  		return false, fmt.Errorf("omgwtfbbq")
   139  	}
   140  	popped, rest, err = Filter(entities, returnErr)
   141  	if assert.Error(t, err, "expected Filter to propagate err from test func") {
   142  		assert.Equal(t, err.Error(), "omgwtfbbq")
   143  	}
   144  }
   145  
   146  func TestHasName(t *testing.T) {
   147  	entities, err := parseYAMLFromStrings(testyaml.DoggosDeploymentYaml, testyaml.SnackYaml)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  	if len(entities) != 2 {
   152  		t.Fatalf("expected 2 entites, got %d: %v", len(entities), entities)
   153  	}
   154  
   155  	doggos := entities[0]
   156  	assert.True(t, doggos.HasName(testyaml.DoggosName))
   157  
   158  	snack := entities[1]
   159  	assert.False(t, snack.HasName(testyaml.DoggosName))
   160  }
   161  
   162  func TestHasNamespace(t *testing.T) {
   163  	entities, err := parseYAMLFromStrings(testyaml.DoggosDeploymentYaml, testyaml.SnackYaml)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	if len(entities) != 2 {
   168  		t.Fatalf("expected 2 entites, got %d: %v", len(entities), entities)
   169  	}
   170  
   171  	doggos := entities[0]
   172  	assert.True(t, doggos.HasNamespace(testyaml.DoggosNamespace))
   173  
   174  	snack := entities[1]
   175  	assert.False(t, snack.HasNamespace(testyaml.DoggosNamespace))
   176  }
   177  
   178  func TestHasKind(t *testing.T) {
   179  	entities, err := parseYAMLFromStrings(testyaml.DoggosDeploymentYaml, testyaml.DoggosServiceYaml)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if len(entities) != 2 {
   184  		t.Fatalf("expected 2 entites, got %d: %v", len(entities), entities)
   185  	}
   186  
   187  	depl := entities[0]
   188  	assert.True(t, depl.HasKind("deployment"))
   189  	assert.False(t, depl.HasKind("service"))
   190  
   191  	svc := entities[1]
   192  	assert.False(t, svc.HasKind("deployment"))
   193  	assert.True(t, svc.HasKind("service"))
   194  }
   195  
   196  func TestSortEntities(t *testing.T) {
   197  	for _, test := range []struct {
   198  		name              string
   199  		inputKindOrder    []string
   200  		expectedKindOrder []string
   201  	}{
   202  		{"all explicitly sorted",
   203  			[]string{"Deployment", "Namespace", "Service"},
   204  			[]string{"Namespace", "Service", "Deployment"},
   205  		},
   206  		{"preserve order if not explicitly sorted",
   207  			[]string{"custom1", "custom2", "custom3"},
   208  			[]string{"custom1", "custom2", "custom3"},
   209  		},
   210  		{"preserve order if not explicitly sorted, also sort others",
   211  			[]string{"custom1", "custom2", "Secret", "custom3", "ConfigMap"},
   212  			[]string{"ConfigMap", "Secret", "custom1", "custom2", "custom3"},
   213  		},
   214  		{"pod and job not sorted",
   215  			[]string{"Pod", "Job", "Job", "Pod"},
   216  			[]string{"Pod", "Job", "Job", "Pod"},
   217  		},
   218  		{"preserve order if not explicitly sorted if many elements",
   219  			// sort.Sort started by comparing input[0] and input[6], which resulted in unexpected order.
   220  			// (didn't preserve order of "Job" vs. "Pod"). Make sure that doesn't happen anymore.
   221  			[]string{"Job", "PersistentVolumeClaim", "Service", "Pod", "ConfigMap", "PersistentVolume", "StatefulSet"},
   222  			[]string{"PersistentVolume", "PersistentVolumeClaim", "ConfigMap", "Service", "StatefulSet", "Job", "Pod"},
   223  		},
   224  	} {
   225  		t.Run(string(test.name), func(t *testing.T) {
   226  			input := entitiesWithKinds(test.inputKindOrder)
   227  			sorted := SortedEntities(input)
   228  			assertKindOrder(t, test.expectedKindOrder, sorted, "sorted entities")
   229  		})
   230  	}
   231  }
   232  
   233  func TestMutableAndImmutableEntities(t *testing.T) {
   234  	for _, test := range []struct {
   235  		name                       string
   236  		inputKindOrder             []string
   237  		expectedMutableKindOrder   []string
   238  		expectedImmutableKindOrder []string
   239  	}{
   240  		{"only mutable",
   241  			[]string{"Deployment", "Namespace", "Service"},
   242  			[]string{"Deployment", "Namespace", "Service"},
   243  			[]string{},
   244  		},
   245  		{"only immutable",
   246  			[]string{"Job", "Pod"},
   247  			[]string{},
   248  			[]string{"Job", "Pod"},
   249  		},
   250  		{"mutable and immutable interspersed",
   251  			[]string{"Deployment", "Job", "Namespace", "Pod", "Service"},
   252  			[]string{"Deployment", "Namespace", "Service"},
   253  			[]string{"Job", "Pod"},
   254  		},
   255  		{"no explicitly sorted kinds are immutable",
   256  			// If any kinds in the explicit sort list are also immutable, things will get weird
   257  			kustomize.OrderFirst,
   258  			kustomize.OrderFirst,
   259  			[]string{},
   260  		},
   261  	} {
   262  		t.Run(string(test.name), func(t *testing.T) {
   263  			input := entitiesWithKinds(test.inputKindOrder)
   264  			mutable, immutable := MutableAndImmutableEntities(input)
   265  			assertKindOrder(t, test.expectedMutableKindOrder, mutable, "mutable entities")
   266  			assertKindOrder(t, test.expectedImmutableKindOrder, immutable, "immutable entities")
   267  		})
   268  	}
   269  }
   270  
   271  func entitiesWithKinds(kinds []string) []K8sEntity {
   272  	entities := make([]K8sEntity, len(kinds))
   273  	for i, k := range kinds {
   274  		entities[i] = entityWithKind(k)
   275  	}
   276  	return entities
   277  }
   278  
   279  func entityWithKind(kind string) K8sEntity {
   280  	return K8sEntity{
   281  		Obj: fakeObject{
   282  			kind: fakeKind(kind),
   283  		},
   284  	}
   285  }
   286  
   287  type fakeObject struct {
   288  	kind fakeKind
   289  }
   290  
   291  func (obj fakeObject) GetObjectKind() schema.ObjectKind { return obj.kind }
   292  func (obj fakeObject) DeepCopyObject() runtime.Object   { return obj }
   293  
   294  type fakeKind string
   295  
   296  func (k fakeKind) SetGroupVersionKind(gvk schema.GroupVersionKind) { panic("unsupported") }
   297  func (k fakeKind) GroupVersionKind() schema.GroupVersionKind {
   298  	return schema.GroupVersionKind{Kind: string(k)}
   299  }
   300  
   301  func assertKindOrder(t *testing.T, expectedKinds []string, actual []K8sEntity, msg string) {
   302  	require.Len(t, actual, len(expectedKinds), "len(expectedKinds) != len(actualKinds): "+msg)
   303  	actualKinds := make([]string, len(expectedKinds))
   304  	for i, e := range actual {
   305  		actualKinds[i] = e.GVK().Kind
   306  	}
   307  	assert.Equal(t, expectedKinds, actualKinds, msg)
   308  }
   309  
   310  func parseYAMLFromStrings(yaml ...string) ([]K8sEntity, error) {
   311  	var res []K8sEntity
   312  	for _, s := range yaml {
   313  		entities, err := ParseYAMLFromString(s)
   314  		if err != nil {
   315  			return nil, err
   316  		}
   317  		res = append(res, entities...)
   318  	}
   319  	return res, nil
   320  }
   321  
   322  func mustParseYAML(t *testing.T, yaml string) []K8sEntity {
   323  	entities, err := ParseYAMLFromString(yaml)
   324  	if err != nil {
   325  		t.Fatalf("ERROR %v parsing k8s YAML:\n%s", err, yaml)
   326  	}
   327  	return entities
   328  }