1
2
3
4 package resource_test
5
6 import (
7 "fmt"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 "sigs.k8s.io/kustomize/api/internal/loader"
14 . "sigs.k8s.io/kustomize/api/resource"
15 "sigs.k8s.io/kustomize/api/types"
16 "sigs.k8s.io/kustomize/kyaml/filesys"
17 "sigs.k8s.io/kustomize/kyaml/kio"
18 )
19
20 func TestRNodesFromBytes(t *testing.T) {
21 type testCase struct {
22 input string
23 expected []string
24 }
25 testCases := map[string]testCase{
26 "empty1": {
27 input: "",
28 expected: []string{},
29 },
30 "empty2": {
31 input: `
32 ---
33 ---
34 `,
35 expected: []string{},
36 },
37 "deployment1": {
38 input: `
39 apiVersion: apps/v1
40 kind: Deployment
41 metadata:
42 name: pooh
43 ---
44 `,
45 expected: []string{
46 `apiVersion: apps/v1
47 kind: Deployment
48 metadata:
49 name: pooh
50 `,
51 },
52 },
53 "deployment2": {
54 input: `
55 apiVersion: apps/v1
56 kind: Deployment
57 metadata:
58 annotations:
59 baseAnno: This is a base annotation
60 labels:
61 app: mungebot
62 foo: bar
63 name: baseprefix-mungebot
64 spec:
65 replicas: 1
66 selector:
67 matchLabels:
68 foo: bar
69 template:
70 metadata:
71 annotations:
72 baseAnno: This is a base annotation
73 labels:
74 app: mungebot
75 foo: bar
76 spec:
77 containers:
78 - env:
79 - name: foo
80 value: bar
81 image: nginx
82 name: nginx
83 ports:
84 - containerPort: 80
85 ---
86 apiVersion: v1
87 kind: Service
88 metadata:
89 annotations:
90 baseAnno: This is a base annotation
91 labels:
92 app: mungebot
93 foo: bar
94 name: baseprefix-mungebot-service
95 spec:
96 ports:
97 - port: 7002
98 selector:
99 app: mungebot
100 foo: bar
101 `,
102 expected: []string{
103 `apiVersion: apps/v1
104 kind: Deployment
105 metadata:
106 annotations:
107 baseAnno: This is a base annotation
108 labels:
109 app: mungebot
110 foo: bar
111 name: baseprefix-mungebot
112 spec:
113 replicas: 1
114 selector:
115 matchLabels:
116 foo: bar
117 template:
118 metadata:
119 annotations:
120 baseAnno: This is a base annotation
121 labels:
122 app: mungebot
123 foo: bar
124 spec:
125 containers:
126 - env:
127 - name: foo
128 value: bar
129 image: nginx
130 name: nginx
131 ports:
132 - containerPort: 80
133 `,
134 `apiVersion: v1
135 kind: Service
136 metadata:
137 annotations:
138 baseAnno: This is a base annotation
139 labels:
140 app: mungebot
141 foo: bar
142 name: baseprefix-mungebot-service
143 spec:
144 ports:
145 - port: 7002
146 selector:
147 app: mungebot
148 foo: bar
149 `,
150 },
151 },
152 }
153
154 for name := range testCases {
155 tc := testCases[name]
156 t.Run(name, func(t *testing.T) {
157 result, err := factory.RNodesFromBytes([]byte(tc.input))
158 if err != nil {
159 t.Fatalf("%v: fails with err: %v", name, err)
160 }
161 if len(result) != len(tc.expected) {
162 for i := range result {
163 str, err := result[i].String()
164 if err != nil {
165 t.Fatalf("%v: result to YAML fails with err: %v", name, err)
166 }
167 t.Logf("--- %d:\n%s", i, str)
168 }
169 t.Fatalf(
170 "%v: actual len %d != expected len %d",
171 name, len(result), len(tc.expected))
172 }
173 for i := range tc.expected {
174 str, err := result[i].String()
175 if err != nil {
176 t.Fatalf("%v: result to YAML fails with err: %v", name, err)
177 }
178 if str != tc.expected[i] {
179 t.Fatalf(
180 "%v: string mismatch in item %d\n"+
181 "actual:\n-----\n%s\n-----\n"+
182 "expected:\n-----\n%s\n-----\n",
183 name, i, str, tc.expected[i])
184 }
185 }
186 })
187 }
188 }
189
190 func TestSliceFromPatches(t *testing.T) {
191 patchGood1 := types.PatchStrategicMerge("patch1.yaml")
192 patch1 := `
193 apiVersion: apps/v1
194 kind: Deployment
195 metadata:
196 name: pooh
197 `
198 patchGood2 := types.PatchStrategicMerge("patch2.yaml")
199 patch2 := `
200 apiVersion: v1
201 kind: ConfigMap
202 metadata:
203 name: winnie
204 namespace: hundred-acre-wood
205 ---
206 # some comment
207 ---
208 ---
209 `
210 patchBad := types.PatchStrategicMerge("patch3.yaml")
211 patch3 := `
212 WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOT: woot
213 `
214 patchList := types.PatchStrategicMerge("patch4.yaml")
215 patch4 := `
216 apiVersion: v1
217 kind: List
218 items:
219 - apiVersion: apps/v1
220 kind: Deployment
221 metadata:
222 name: pooh
223 - apiVersion: v1
224 kind: ConfigMap
225 metadata:
226 name: winnie
227 namespace: hundred-acre-wood
228 `
229 patchList2 := types.PatchStrategicMerge("patch5.yaml")
230 patch5 := `
231 apiVersion: v1
232 kind: DeploymentList
233 items:
234 - apiVersion: apps/v1
235 kind: Deployment
236 metadata:
237 name: deployment-a
238 spec: &hostAliases
239 template:
240 spec:
241 hostAliases:
242 - hostnames:
243 - a.example.com
244 ip: 8.8.8.8
245 - apiVersion: apps/v1
246 kind: Deployment
247 metadata:
248 name: deployment-b
249 spec:
250 <<: *hostAliases
251 `
252 patchList3 := types.PatchStrategicMerge("patch6.yaml")
253 patch6 := `
254 apiVersion: v1
255 kind: List
256 items:
257 `
258 patchList4 := types.PatchStrategicMerge("patch7.yaml")
259 patch7 := `
260 apiVersion: v1
261 kind: List
262 `
263 testDeploymentSpec := map[string]interface{}{
264 "template": map[string]interface{}{
265 "spec": map[string]interface{}{
266 "hostAliases": []interface{}{
267 map[string]interface{}{
268 "hostnames": []interface{}{
269 "a.example.com",
270 },
271 "ip": "8.8.8.8",
272 },
273 },
274 },
275 },
276 }
277 testDeploymentA := factory.FromMap(
278 map[string]interface{}{
279 "apiVersion": "apps/v1",
280 "kind": "Deployment",
281 "metadata": map[string]interface{}{
282 "name": "deployment-a",
283 },
284 "spec": testDeploymentSpec,
285 })
286 testDeploymentB := factory.FromMap(
287 map[string]interface{}{
288 "apiVersion": "apps/v1",
289 "kind": "Deployment",
290 "metadata": map[string]interface{}{
291 "name": "deployment-b",
292 },
293 "spec": testDeploymentSpec,
294 })
295
296 fSys := filesys.MakeFsInMemory()
297 fSys.WriteFile(string(patchGood1), []byte(patch1))
298 fSys.WriteFile(string(patchGood2), []byte(patch2))
299 fSys.WriteFile(string(patchBad), []byte(patch3))
300 fSys.WriteFile(string(patchList), []byte(patch4))
301 fSys.WriteFile(string(patchList2), []byte(patch5))
302 fSys.WriteFile(string(patchList3), []byte(patch6))
303 fSys.WriteFile(string(patchList4), []byte(patch7))
304
305 ldr, err := loader.NewLoader(
306 loader.RestrictionRootOnly, filesys.Separator, fSys)
307 if err != nil {
308 t.Fatal(err)
309 }
310
311 tests := map[string]struct {
312 input []types.PatchStrategicMerge
313 expectedOut []*Resource
314 expectedErr bool
315 }{
316 "happy": {
317 input: []types.PatchStrategicMerge{patchGood1, patchGood2},
318 expectedOut: []*Resource{testDeployment, testConfigMap},
319 expectedErr: false,
320 },
321 "badFileName": {
322 input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
323 expectedOut: []*Resource{},
324 expectedErr: true,
325 },
326 "badData": {
327 input: []types.PatchStrategicMerge{patchGood1, patchBad},
328 expectedOut: []*Resource{},
329 expectedErr: true,
330 },
331 "listOfPatches": {
332 input: []types.PatchStrategicMerge{patchList},
333 expectedOut: []*Resource{testDeployment, testConfigMap},
334 expectedErr: false,
335 },
336 "listWithAnchorReference": {
337 input: []types.PatchStrategicMerge{patchList2},
338 expectedOut: []*Resource{testDeploymentA, testDeploymentB},
339 expectedErr: false,
340 },
341 "listWithNoEntries": {
342 input: []types.PatchStrategicMerge{patchList3},
343 expectedOut: []*Resource{},
344 expectedErr: false,
345 },
346 "listWithNoItems": {
347 input: []types.PatchStrategicMerge{patchList4},
348 expectedOut: []*Resource{},
349 expectedErr: false,
350 },
351 }
352 for n, test := range tests {
353 t.Run(n, func(t *testing.T) {
354 rs, err := factory.SliceFromPatches(ldr, test.input)
355 if err != nil {
356 assert.True(t, test.expectedErr,
357 fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
358 return
359 }
360 assert.False(t, test.expectedErr, "expected no error in "+n)
361 assert.Equal(t, len(test.expectedOut), len(rs))
362 for i := range rs {
363 expYaml, err := test.expectedOut[i].AsYAML()
364 require.NoError(t, err)
365 actYaml, err := rs[i].AsYAML()
366 require.NoError(t, err)
367 assert.Equal(t, expYaml, actYaml)
368 }
369 })
370 }
371 }
372
373 func TestHash(t *testing.T) {
374 input := `
375 apiVersion: v1
376 kind: ConfigMap
377 metadata:
378 name: foo
379 data:
380 one: ""
381 binaryData:
382 two: ""
383 `
384 expect := "698h7c7t9m"
385 k, err := factory.SliceFromBytes([]byte(input))
386 if err != nil {
387 t.Fatal(err)
388 }
389 result, err := k[0].Hash(factory.Hasher())
390 if err != nil {
391 t.Fatal(err)
392 }
393 if result != expect {
394 t.Fatalf("expect %s but got %s", expect, result)
395 }
396 }
397
398 func TestMoreRNodesFromBytes(t *testing.T) {
399 type expected struct {
400 out []string
401 isErr bool
402 }
403 testCases := map[string]struct {
404 input []byte
405 exp expected
406 }{
407 "garbage": {
408 input: []byte("garbageIn: garbageOut"),
409 exp: expected{
410 isErr: true,
411 },
412 },
413 "noBytes": {
414 input: []byte{},
415 exp: expected{},
416 },
417 "goodJson": {
418 input: []byte(`
419 {"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
420 `),
421 exp: expected{
422 out: []string{
423 `{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
424 },
425 },
426 },
427 "goodYaml1": {
428 input: []byte(`
429 apiVersion: v1
430 kind: ConfigMap
431 metadata:
432 name: winnie
433 `),
434 exp: expected{
435 out: []string{`
436 apiVersion: v1
437 kind: ConfigMap
438 metadata:
439 name: winnie
440 `},
441 },
442 },
443 "goodYaml2": {
444 input: []byte(`
445 apiVersion: v1
446 kind: ConfigMap
447 metadata:
448 name: winnie
449 ---
450 apiVersion: v1
451 kind: ConfigMap
452 metadata:
453 name: winnie
454 `),
455 exp: expected{
456 out: []string{`
457 apiVersion: v1
458 kind: ConfigMap
459 metadata:
460 name: winnie
461 `, `
462 apiVersion: v1
463 kind: ConfigMap
464 metadata:
465 name: winnie
466 `},
467 },
468 },
469 "garbageInOneOfTwoObjects": {
470 input: []byte(`
471 apiVersion: v1
472 kind: ConfigMap
473 metadata:
474 name: winnie
475 ---
476 WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
477 `),
478 exp: expected{
479 isErr: true,
480 },
481 },
482 "emptyObjects": {
483 input: []byte(`
484 ---
485 #a comment
486
487 ---
488
489 `),
490 exp: expected{
491 out: []string{},
492 },
493 },
494 "Missing .metadata.name in object": {
495 input: []byte(`
496 apiVersion: v1
497 kind: Namespace
498 metadata:
499 annotations:
500 foo: bar
501 `),
502 exp: expected{
503 isErr: true,
504 },
505 },
506 "nil value in list": {
507 input: []byte(`
508 apiVersion: builtin
509 kind: ConfigMapGenerator
510 metadata:
511 name: kube100-site
512 labels:
513 app: web
514 testList:
515 - testA
516 -
517 `),
518 exp: expected{
519 isErr: true,
520 },
521 },
522 "List": {
523 input: []byte(`
524 apiVersion: v1
525 kind: List
526 items:
527 - apiVersion: v1
528 kind: ConfigMap
529 metadata:
530 name: winnie
531 - apiVersion: v1
532 kind: ConfigMap
533 metadata:
534 name: winnie
535 `),
536 exp: expected{
537 out: []string{`
538 apiVersion: v1
539 kind: ConfigMap
540 metadata:
541 name: winnie
542 `, `
543 apiVersion: v1
544 kind: ConfigMap
545 metadata:
546 name: winnie
547 `,
548 },
549 },
550 },
551 "ConfigMapList": {
552 input: []byte(`
553 apiVersion: v1
554 kind: ConfigMapList
555 items:
556 - apiVersion: v1
557 kind: ConfigMap
558 metadata:
559 name: winnie
560 - apiVersion: v1
561 kind: ConfigMap
562 metadata:
563 name: winnie
564 `),
565 exp: expected{
566 out: []string{
567 `{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
568 `{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
569 },
570 },
571 },
572 "listWithAnchors": {
573 input: []byte(`
574 apiVersion: v1
575 kind: DeploymentList
576 items:
577 - apiVersion: apps/v1
578 kind: Deployment
579 metadata:
580 name: deployment-a
581 spec: &foo
582 template:
583 spec:
584 hostAliases:
585 - hostnames:
586 - a.example.com
587 ip: 8.8.8.8
588 - apiVersion: apps/v1
589 kind: Deployment
590 metadata:
591 name: deployment-b
592 spec:
593 *foo
594 `),
595 exp: expected{
596 out: []string{
597 `{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-a"}, ` +
598 `"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`,
599 `{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-b"}, ` +
600 `"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`},
601 },
602 },
603 "simpleAnchor": {
604 input: []byte(`
605 apiVersion: v1
606 kind: ConfigMap
607 metadata:
608 name: wildcard
609 data:
610 color: &color-used blue
611 feeling: *color-used
612 `),
613 exp: expected{
614 out: []string{`
615 apiVersion: v1
616 kind: ConfigMap
617 metadata:
618 name: wildcard
619 data:
620 color: blue
621 feeling: blue
622 `},
623 },
624 },
625 }
626 for n := range testCases {
627 tc := testCases[n]
628 t.Run(n, func(t *testing.T) {
629 rs, err := factory.RNodesFromBytes(tc.input)
630 if err != nil {
631 assert.True(t, tc.exp.isErr)
632 return
633 }
634 assert.False(t, tc.exp.isErr)
635 assert.Equal(t, len(tc.exp.out), len(rs))
636 for i := range rs {
637 actual, err := rs[i].String()
638 require.NoError(t, err)
639 assert.Equal(
640 t, strings.TrimSpace(tc.exp.out[i]), strings.TrimSpace(actual))
641 }
642 })
643 }
644 }
645
646 func TestDropLocalNodes(t *testing.T) {
647 testCases := map[string]struct {
648 input []byte
649 expected []byte
650 }{
651 "localConfigUnset": {
652 input: []byte(`apiVersion: v1
653 kind: ConfigMap
654 metadata:
655 name: winnie
656 `),
657 expected: []byte(`apiVersion: v1
658 kind: ConfigMap
659 metadata:
660 name: winnie
661 `),
662 },
663 "localConfigSet": {
664 input: []byte(`apiVersion: v1
665 kind: ConfigMap
666 metadata:
667 name: winnie-skip
668 annotations:
669 # this annotation causes the Resource to be ignored by kustomize
670 config.kubernetes.io/local-config: ""
671 `),
672 expected: nil,
673 },
674 "localConfigSetToTrue": {
675 input: []byte(`apiVersion: v1
676 kind: ConfigMap
677 metadata:
678 name: winnie-skip
679 annotations:
680 config.kubernetes.io/local-config: "true"
681 `),
682 expected: nil,
683 },
684 "localConfigSetToFalse": {
685 input: []byte(`apiVersion: v1
686 kind: ConfigMap
687 metadata:
688 name: winnie
689 annotations:
690 config.kubernetes.io/local-config: "false"
691 `),
692 expected: []byte(`apiVersion: v1
693 kind: ConfigMap
694 metadata:
695 annotations:
696 config.kubernetes.io/local-config: "false"
697 name: winnie
698 `),
699 },
700 "localConfigMultiInput": {
701 input: []byte(`apiVersion: v1
702 kind: ConfigMap
703 metadata:
704 name: winnie
705 ---
706 apiVersion: v1
707 kind: ConfigMap
708 metadata:
709 name: winnie-skip
710 annotations:
711 config.kubernetes.io/local-config: "true"
712 `),
713 expected: []byte(`apiVersion: v1
714 kind: ConfigMap
715 metadata:
716 name: winnie
717 `),
718 },
719 }
720 for n := range testCases {
721 tc := testCases[n]
722 t.Run(n, func(t *testing.T) {
723 nin, _ := kio.FromBytes(tc.input)
724 res, err := factory.DropLocalNodes(nin)
725 require.NoError(t, err)
726 if tc.expected == nil {
727 assert.Equal(t, 0, len(res))
728 } else {
729 actual, _ := res[0].AsYAML()
730 assert.Equal(t, tc.expected, actual)
731 }
732 })
733 }
734 }
735
View as plain text