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