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 }