1
2
3
4 package runfn
5
6 import (
7 "bytes"
8 "fmt"
9 "os"
10 "os/user"
11 "path/filepath"
12 "runtime"
13 "strings"
14 "testing"
15
16 "github.com/stretchr/testify/assert"
17 "sigs.k8s.io/kustomize/kyaml/copyutil"
18 "sigs.k8s.io/kustomize/kyaml/errors"
19 "sigs.k8s.io/kustomize/kyaml/filesys"
20 "sigs.k8s.io/kustomize/kyaml/fn/runtime/container"
21 "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
22 "sigs.k8s.io/kustomize/kyaml/kio"
23 "sigs.k8s.io/kustomize/kyaml/kio/filters"
24 "sigs.k8s.io/kustomize/kyaml/yaml"
25 )
26
27 const (
28 ValueReplacerYAMLData = `apiVersion: v1
29 kind: ValueReplacer
30 metadata:
31 annotations:
32 config.kubernetes.io/function: |
33 container:
34 image: gcr.io/example.com/image:version
35 config.kubernetes.io/local-config: "true"
36 stringMatch: Deployment
37 replace: StatefulSet
38 `
39 )
40
41 func currentUser() (*user.User, error) {
42 return &user.User{
43 Uid: "1",
44 Gid: "2",
45 }, nil
46 }
47
48 func TestRunFns_init(t *testing.T) {
49 instance := RunFns{}
50 instance.init()
51 if !assert.Equal(t, instance.Input, os.Stdin) {
52 t.FailNow()
53 }
54 if !assert.Equal(t, instance.Output, os.Stdout) {
55 t.FailNow()
56 }
57
58 api, err := yaml.Parse(`apiVersion: apps/v1
59 kind:
60 `)
61 spec := runtimeutil.FunctionSpec{
62 Container: runtimeutil.ContainerSpec{
63 Image: "example.com:version",
64 },
65 }
66 if !assert.NoError(t, err) {
67 return
68 }
69 filter, _ := instance.functionFilterProvider(spec, api, currentUser)
70 c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
71 cf := &c
72 cf.Exec.FunctionConfig = api
73 assert.Equal(t, cf, filter)
74 }
75
76 func TestRunFns_initAsCurrentUser(t *testing.T) {
77 instance := RunFns{
78 AsCurrentUser: true,
79 }
80 instance.init()
81 if !assert.Equal(t, instance.Input, os.Stdin) {
82 t.FailNow()
83 }
84 if !assert.Equal(t, instance.Output, os.Stdout) {
85 t.FailNow()
86 }
87
88 api, err := yaml.Parse(`apiVersion: apps/v1
89 kind:
90 `)
91 spec := runtimeutil.FunctionSpec{
92 Container: runtimeutil.ContainerSpec{
93 Image: "example.com:version",
94 },
95 }
96 if !assert.NoError(t, err) {
97 return
98 }
99 filter, _ := instance.functionFilterProvider(spec, api, currentUser)
100 c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "1:2")
101 cf := &c
102 cf.Exec.FunctionConfig = api
103 assert.Equal(t, cf, filter)
104 }
105
106 func TestRunFns_Execute__initGlobalScope(t *testing.T) {
107 instance := RunFns{GlobalScope: true}
108 instance.init()
109 if !assert.Equal(t, instance.Input, os.Stdin) {
110 t.FailNow()
111 }
112 if !assert.Equal(t, instance.Output, os.Stdout) {
113 t.FailNow()
114 }
115 api, err := yaml.Parse(`apiVersion: apps/v1
116 kind:
117 `)
118 if !assert.NoError(t, err) {
119 return
120 }
121
122 spec := runtimeutil.FunctionSpec{
123 Container: runtimeutil.ContainerSpec{
124 Image: "example.com:version",
125 },
126 }
127 if !assert.NoError(t, err) {
128 return
129 }
130 filter, _ := instance.functionFilterProvider(spec, api, currentUser)
131 c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
132 cf := &c
133 cf.Exec.FunctionConfig = api
134 cf.Exec.GlobalScope = true
135 assert.Equal(t, cf, filter)
136 }
137
138 func TestRunFns_Execute__initDefault(t *testing.T) {
139 b := &bytes.Buffer{}
140 var tests = []struct {
141 instance RunFns
142 expected RunFns
143 name string
144 }{
145 {
146 instance: RunFns{},
147 name: "empty",
148 expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
149 },
150 {
151 name: "explicit output",
152 instance: RunFns{Output: b},
153 expected: RunFns{Output: b, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
154 },
155 {
156 name: "explicit input",
157 instance: RunFns{Input: b},
158 expected: RunFns{Output: os.Stdout, Input: b, NoFunctionsFromInput: getFalse()},
159 },
160 {
161 name: "explicit functions -- no functions from input",
162 instance: RunFns{Functions: []*yaml.RNode{{}}},
163 expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getTrue(), Functions: []*yaml.RNode{{}}},
164 },
165 {
166 name: "explicit functions -- yes functions from input",
167 instance: RunFns{Functions: []*yaml.RNode{{}}, NoFunctionsFromInput: getFalse()},
168 expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse(), Functions: []*yaml.RNode{{}}},
169 },
170 {
171 name: "explicit functions in paths -- no functions from input",
172 instance: RunFns{FunctionPaths: []string{"foo"}},
173 expected: RunFns{
174 Output: os.Stdout,
175 Input: os.Stdin,
176 NoFunctionsFromInput: getTrue(),
177 FunctionPaths: []string{"foo"},
178 },
179 },
180 {
181 name: "functions in paths -- yes functions from input",
182 instance: RunFns{FunctionPaths: []string{"foo"}, NoFunctionsFromInput: getFalse()},
183 expected: RunFns{
184 Output: os.Stdout,
185 Input: os.Stdin,
186 NoFunctionsFromInput: getFalse(),
187 FunctionPaths: []string{"foo"},
188 },
189 },
190 {
191 name: "explicit directories in mounts",
192 instance: RunFns{StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}},
193 expected: RunFns{
194 Output: os.Stdout,
195 Input: os.Stdin,
196 NoFunctionsFromInput: getFalse(),
197 StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}},
198 },
199 },
200 }
201 for i := range tests {
202 tt := tests[i]
203 t.Run(tt.name, func(t *testing.T) {
204 (&tt.instance).init()
205 (&tt.instance).functionFilterProvider = nil
206 if !assert.Equal(t, tt.expected, tt.instance) {
207 t.FailNow()
208 }
209 })
210 }
211 }
212
213 func getTrue() *bool {
214 t := true
215 return &t
216 }
217
218 func getFalse() *bool {
219 f := false
220 return &f
221 }
222
223
224 func TestRunFns_getFilters(t *testing.T) {
225 type f struct {
226
227 path, value string
228
229
230 outOfPackage bool
231
232
233 explicitFunction bool
234
235
236
237
238
239 newFnPath bool
240 }
241 var tests = []struct {
242
243 in []f
244
245 out []string
246
247
248 outFn func(string) []string
249
250
251 error string
252
253
254 name string
255
256 noFunctionsFromInput *bool
257
258 enableStarlark bool
259
260 disableContainers bool
261 }{
262
263
264
265 {name: "single implicit function",
266 in: []f{
267 {
268 path: filepath.Join("foo", "bar.yaml"),
269 value: `
270 apiVersion: example.com/v1alpha1
271 kind: ExampleFunction
272 metadata:
273 annotations:
274 config.kubernetes.io/function: |
275 container:
276 image: gcr.io/example.com/image:v1.0.0
277 config.kubernetes.io/local-config: "true"
278 `,
279 },
280 },
281 out: []string{"gcr.io/example.com/image:v1.0.0"},
282 },
283
284 {
285 name: "no function spec",
286 in: []f{
287 {
288 explicitFunction: true,
289 value: `
290 apiVersion: example.com/v1alpha1
291 kind: ExampleFunction
292 metadata:
293 annotations:
294 foo: bar
295 `,
296 },
297 },
298 },
299 {
300 name: "invalid input object",
301 in: []f{
302 {
303 explicitFunction: true,
304 value: `
305 foo: bar
306 `,
307 },
308 },
309 error: "failed to get FunctionSpec: failed to get ResourceMeta: missing Resource metadata",
310 },
311
312
313
314
315 {name: "defer_failure",
316 in: []f{
317 {
318 path: filepath.Join("foo", "bar.yaml"),
319 value: `
320 apiVersion: example.com/v1alpha1
321 kind: ExampleFunction
322 metadata:
323 annotations:
324 config.kubernetes.io/function: |
325 deferFailure: true
326 container:
327 image: gcr.io/example.com/image:v1.0.0
328 config.kubernetes.io/local-config: "true"
329 `,
330 },
331 },
332 out: []string{"gcr.io/example.com/image:v1.0.0 deferFailure: true"},
333 },
334 {
335 name: "parse_failure",
336 in: []f{
337 {
338 path: filepath.Join("foo", "bar.yaml"),
339 value: `
340 apiVersion: example.com/v1alpha1
341 kind: ExampleFunction
342 metadata:
343 annotations:
344 config.kubernetes.io/function: |
345 containeeer:
346 image: gcr.io/example.com/image:v1.0.0
347 `,
348 },
349 },
350 error: "config.kubernetes.io/function unmarshal error: error unmarshaling JSON: while decoding JSON: json: unknown field \"containeeer\"",
351 },
352
353 {name: "disable containers",
354 in: []f{
355 {
356 path: filepath.Join("foo", "bar.yaml"),
357 value: `
358 apiVersion: example.com/v1alpha1
359 kind: ExampleFunction
360 metadata:
361 annotations:
362 config.kubernetes.io/function: |
363 container:
364 image: gcr.io/example.com/image:v1.0.0
365 config.kubernetes.io/local-config: "true"
366 `,
367 },
368 },
369 out: nil,
370 disableContainers: true,
371 },
372
373
374
375
376 {name: "sort functions -- deepest first",
377 in: []f{
378 {
379 path: "a.yaml",
380 value: `
381 metadata:
382 annotations:
383 config.kubernetes.io/function: |
384 container:
385 image: a
386 `,
387 },
388 {
389 path: filepath.Join("foo", "b.yaml"),
390 value: `
391 metadata:
392 annotations:
393 config.kubernetes.io/function: |
394 container:
395 image: b
396 `,
397 },
398 },
399 out: []string{"b", "a"},
400 },
401
402
403
404
405 {name: "sort functions -- skip implicit with output of package",
406 in: []f{
407 {
408 path: filepath.Join("foo", "a.yaml"),
409 outOfPackage: true,
410 value: `
411 metadata:
412 annotations:
413 config.kubernetes.io/function: |
414 container:
415 image: a
416 `,
417 },
418 {
419 path: "b.yaml",
420 value: `
421 metadata:
422 annotations:
423 config.kubernetes.io/function: |
424 container:
425 image: b
426 `,
427 },
428 },
429 out: []string{"a"},
430 },
431
432
433
434
435 {name: "sort functions -- skip implicit",
436 noFunctionsFromInput: getTrue(),
437 in: []f{
438 {
439 path: filepath.Join("foo", "a.yaml"),
440 value: `
441 metadata:
442 annotations:
443 config.kubernetes.io/function: |
444 container:
445 image: a
446 `,
447 },
448 {
449 path: "b.yaml",
450 value: `
451 metadata:
452 annotations:
453 config.kubernetes.io/function: |
454 container:
455 image: b
456 `,
457 },
458 },
459 out: nil,
460 },
461
462
463
464
465 {name: "sort functions -- include implicit",
466 noFunctionsFromInput: getFalse(),
467 in: []f{
468 {
469 path: filepath.Join("foo", "a.yaml"),
470 value: `
471 metadata:
472 annotations:
473 config.kubernetes.io/function: |
474 container:
475 image: a
476 `,
477 },
478 {
479 path: "b.yaml",
480 value: `
481 metadata:
482 annotations:
483 config.kubernetes.io/function: |
484 container:
485 image: b
486 `,
487 },
488 },
489 out: []string{"a", "b"},
490 },
491
492
493
494
495 {name: "sort functions -- implicit first",
496 noFunctionsFromInput: getFalse(),
497 in: []f{
498 {
499 path: filepath.Join("foo", "a.yaml"),
500 outOfPackage: true,
501 value: `
502 metadata:
503 annotations:
504 config.kubernetes.io/function: |
505 container:
506 image: a
507 `,
508 },
509 {
510 path: "b.yaml",
511 value: `
512 metadata:
513 annotations:
514 config.kubernetes.io/function: |
515 container:
516 image: b
517 `,
518 },
519 },
520 out: []string{"b", "a"},
521 },
522
523
524
525
526 {name: "explicit functions",
527 in: []f{
528 {
529 explicitFunction: true,
530 value: `
531 metadata:
532 annotations:
533 config.kubernetes.io/function: |
534 container:
535 image: c
536 `,
537 },
538 {
539 path: "b.yaml",
540 value: `
541 metadata:
542 annotations:
543 config.kubernetes.io/function: |
544 container:
545 image: b
546 `,
547 },
548 },
549 out: []string{"c"},
550 },
551
552
553
554
555 {name: "sort functions -- implicit first",
556 noFunctionsFromInput: getFalse(),
557 in: []f{
558 {
559 explicitFunction: true,
560 value: `
561 metadata:
562 annotations:
563 config.kubernetes.io/function: |
564 container:
565 image: c
566 `,
567 },
568 {
569 path: filepath.Join("foo", "a.yaml"),
570 outOfPackage: true,
571 value: `
572 metadata:
573 annotations:
574 config.kubernetes.io/function: |
575 container:
576 image: a
577 `,
578 },
579 {
580 path: "b.yaml",
581 value: `
582 metadata:
583 annotations:
584 config.kubernetes.io/function: |
585 container:
586 image: b
587 `,
588 },
589 },
590 out: []string{"b", "a", "c"},
591 },
592
593
594
595
596 {name: "starlark-function",
597 in: []f{
598 {
599 path: filepath.Join("foo", "bar.yaml"),
600 value: `
601 apiVersion: example.com/v1alpha1
602 kind: ExampleFunction
603 metadata:
604 annotations:
605 config.kubernetes.io/function: |
606 starlark:
607 path: a/b/c
608 `,
609 },
610 },
611 enableStarlark: true,
612 outFn: func(path string) []string {
613 return []string{
614 fmt.Sprintf("name: path: %s/foo/a/b/c url: program:", filepath.ToSlash(path))}
615 },
616 },
617
618
619
620
621 {name: "starlark-function-absolute",
622 in: []f{
623 {
624 path: filepath.Join("foo", "bar.yaml"),
625 value: `
626 apiVersion: example.com/v1alpha1
627 kind: ExampleFunction
628 metadata:
629 annotations:
630 config.kubernetes.io/function: |
631 starlark:
632 path: /a/b/c
633 `,
634 },
635 },
636 enableStarlark: true,
637 error: "absolute function path /a/b/c not allowed",
638 },
639
640
641
642
643 {name: "starlark-function-escape-parent",
644 in: []f{
645 {
646 path: filepath.Join("foo", "bar.yaml"),
647 value: `
648 apiVersion: example.com/v1alpha1
649 kind: ExampleFunction
650 metadata:
651 annotations:
652 config.kubernetes.io/function: |
653 starlark:
654 path: ../a/b/c
655 `,
656 },
657 },
658 enableStarlark: true,
659 error: "function path ../a/b/c not allowed to start with ../",
660 },
661
662 {name: "starlark-function-disabled",
663 in: []f{
664 {
665 path: filepath.Join("foo", "bar.yaml"),
666 value: `
667 apiVersion: example.com/v1alpha1
668 kind: ExampleFunction
669 metadata:
670 annotations:
671 config.kubernetes.io/function: |
672 starlark:
673 path: a/b/c
674 `,
675 },
676 },
677 },
678 }
679
680 for i := range tests {
681 tt := tests[i]
682 t.Run(tt.name, func(t *testing.T) {
683
684 d := setupTest(t)
685
686
687 var fnPaths []string
688 var parsedFns []*yaml.RNode
689 var fnPath string
690 var err error
691 for _, f := range tt.in {
692
693 var dir string
694 switch {
695 case f.outOfPackage:
696
697 if f.newFnPath || fnPath == "" {
698
699 fnPath = t.TempDir()
700 fnPaths = append(fnPaths, fnPath)
701 }
702 dir = fnPath
703 case f.explicitFunction:
704 parsedFns = append(parsedFns, yaml.MustParse(f.value))
705 default:
706
707 dir = d
708 }
709
710 if !f.explicitFunction {
711
712 err = os.MkdirAll(filepath.Join(dir, filepath.Dir(f.path)), 0700)
713 if !assert.NoError(t, err) {
714 t.FailNow()
715 }
716 err := os.WriteFile(filepath.Join(dir, f.path), []byte(f.value), 0600)
717 if !assert.NoError(t, err) {
718 t.FailNow()
719 }
720 }
721 }
722
723
724 r := &RunFns{
725 EnableStarlark: tt.enableStarlark,
726 DisableContainers: tt.disableContainers,
727 FunctionPaths: fnPaths,
728 Functions: parsedFns,
729 Path: d,
730 NoFunctionsFromInput: tt.noFunctionsFromInput,
731 }
732 r.init()
733
734
735 var results []string
736 _, fltrs, _, err := r.getNodesAndFilters()
737
738 if tt.error != "" {
739 if !assert.EqualError(t, err, tt.error) {
740 t.FailNow()
741 }
742 return
743 }
744
745 if !assert.NoError(t, err) {
746 t.FailNow()
747 }
748 for _, f := range fltrs {
749 results = append(results, strings.TrimSpace(fmt.Sprintf("%v", f)))
750 }
751
752
753 if tt.outFn != nil {
754 if !assert.Equal(t, tt.outFn(d), results) {
755 t.FailNow()
756 }
757 } else {
758 if !assert.Equal(t, tt.out, results) {
759 t.FailNow()
760 }
761 }
762 })
763 }
764 }
765
766 func TestRunFns_sortFns(t *testing.T) {
767 testCases := []struct {
768 name string
769 nodes []*yaml.RNode
770 expectedImages []string
771 expectedErrMsg string
772 }{
773 {
774 name: "multiple functions in the same file are ordered by index",
775 nodes: []*yaml.RNode{
776 yaml.MustParse(`
777 metadata:
778 annotations:
779 config.kubernetes.io/path: functions.yaml
780 config.kubernetes.io/index: 1
781 config.kubernetes.io/function: |
782 container:
783 image: a
784 `),
785 yaml.MustParse(`
786 metadata:
787 annotations:
788 config.kubernetes.io/path: functions.yaml
789 config.kubernetes.io/index: 0
790 config.kubernetes.io/function: |
791 container:
792 image: b
793 `),
794 },
795 expectedImages: []string{"b", "a"},
796 },
797 {
798 name: "non-integer value in index annotation is an error",
799 nodes: []*yaml.RNode{
800 yaml.MustParse(`
801 metadata:
802 annotations:
803 config.kubernetes.io/path: functions.yaml
804 config.kubernetes.io/index: 0
805 config.kubernetes.io/function: |
806 container:
807 image: a
808 `),
809 yaml.MustParse(`
810 metadata:
811 annotations:
812 config.kubernetes.io/path: functions.yaml
813 config.kubernetes.io/index: abc
814 config.kubernetes.io/function: |
815 container:
816 image: b
817 `),
818 },
819 expectedErrMsg: "strconv.Atoi: parsing \"abc\": invalid syntax",
820 },
821 }
822
823 for i := range testCases {
824 test := testCases[i]
825 t.Run(test.name, func(t *testing.T) {
826 packageBuff := &kio.PackageBuffer{
827 Nodes: test.nodes,
828 }
829
830 err := sortFns(packageBuff)
831 if test.expectedErrMsg != "" {
832 if !assert.Error(t, err) {
833 t.FailNow()
834 }
835 assert.Equal(t, test.expectedErrMsg, err.Error())
836 return
837 }
838
839 if !assert.NoError(t, err) {
840 t.FailNow()
841 }
842
843 var images []string
844 for _, n := range packageBuff.Nodes {
845 spec, err := runtimeutil.GetFunctionSpec(n)
846 if !assert.NoError(t, err) {
847 t.FailNow()
848 }
849 images = append(images, spec.Container.Image)
850 }
851
852 assert.Equal(t, test.expectedImages, images)
853 })
854 }
855 }
856
857 func TestRunFns_network(t *testing.T) {
858 tests := []struct {
859 name string
860 input string
861 network bool
862 expectNetwork bool
863 error string
864 }{
865 {
866 name: "imperative false, declarative false",
867 input: `
868 metadata:
869 annotations:
870 config.kubernetes.io/function: |
871 container:
872 image: a
873 network: false
874 `,
875 network: false,
876 expectNetwork: false,
877 },
878 {
879 name: "imperative true, declarative false",
880 input: `
881 metadata:
882 annotations:
883 config.kubernetes.io/function: |
884 container:
885 image: a
886 network: false
887 `,
888 network: true,
889 expectNetwork: false,
890 },
891 {
892 name: "imperative true, declarative true",
893 input: `
894 metadata:
895 annotations:
896 config.kubernetes.io/function: |
897 container:
898 image: a
899 network: true
900 `,
901 network: true,
902 expectNetwork: true,
903 },
904 {
905 name: "imperative false, declarative true",
906 input: `
907 metadata:
908 annotations:
909 config.kubernetes.io/function: |
910 container:
911 image: a
912 network: true
913 `,
914 network: false,
915 error: "network required but not enabled with --network",
916 },
917 }
918
919 for i := range tests {
920 tt := tests[i]
921 fn := yaml.MustParse(tt.input)
922 t.Run(tt.name, func(t *testing.T) {
923
924 r := &RunFns{
925 Functions: []*yaml.RNode{fn},
926 Network: tt.network,
927 }
928 r.init()
929
930 _, fltrs, _, err := r.getNodesAndFilters()
931 if tt.error != "" {
932 if !assert.EqualError(t, err, tt.error) {
933 t.FailNow()
934 }
935 return
936 }
937 if !assert.NoError(t, err) {
938 t.FailNow()
939 }
940
941 fltr := fltrs[0].(*container.Filter)
942 if !assert.Equal(t, tt.expectNetwork, fltr.Network) {
943 t.FailNow()
944 }
945 })
946 }
947 }
948
949 func TestCmd_Execute(t *testing.T) {
950 dir := setupTest(t)
951
952
953 if !assert.NoError(t, os.WriteFile(
954 filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
955 return
956 }
957
958 instance := RunFns{Path: dir, functionFilterProvider: getFilterProvider(t)}
959 if !assert.NoError(t, instance.Execute()) {
960 t.FailNow()
961 }
962 b, err := os.ReadFile(
963 filepath.Join(dir, "java", "java-deployment.resource.yaml"))
964 if !assert.NoError(t, err) {
965 t.FailNow()
966 }
967 assert.Contains(t, string(b), "kind: StatefulSet")
968 }
969
970 type TestFilter struct {
971 invoked bool
972 Exit error
973 }
974
975 func (f *TestFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
976 f.invoked = true
977 return input, nil
978 }
979
980 func (f *TestFilter) GetExit() error {
981 return f.Exit
982 }
983
984 func TestCmd_Execute_deferFailure(t *testing.T) {
985 dir := setupTest(t)
986
987
988 if !assert.NoError(t, os.WriteFile(
989 filepath.Join(dir, "filter1.yaml"), []byte(`apiVersion: v1
990 kind: ValueReplacer
991 metadata:
992 annotations:
993 config.kubernetes.io/function: |
994 container:
995 image: 1
996 config.kubernetes.io/local-config: "true"
997 stringMatch: Deployment
998 replace: StatefulSet
999 `), 0600)) {
1000 t.FailNow()
1001 }
1002
1003
1004 if !assert.NoError(t, os.WriteFile(
1005 filepath.Join(dir, "filter2.yaml"), []byte(`apiVersion: v1
1006 kind: ValueReplacer
1007 metadata:
1008 annotations:
1009 config.kubernetes.io/function: |
1010 container:
1011 image: 2
1012 config.kubernetes.io/local-config: "true"
1013 stringMatch: Deployment
1014 replace: StatefulSet
1015 `), 0600)) {
1016 t.FailNow()
1017 }
1018
1019 var fltrs []*TestFilter
1020 instance := RunFns{
1021 Path: dir,
1022 functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
1023 tf := &TestFilter{
1024 Exit: errors.Errorf("message: %s", f.Container.Image),
1025 }
1026 fltrs = append(fltrs, tf)
1027 return tf, nil
1028 },
1029 }
1030 instance.init()
1031
1032 err := instance.Execute()
1033
1034
1035 if !assert.Equal(t, 2, len(fltrs)) {
1036 t.FailNow()
1037 }
1038 for i := range fltrs {
1039 if !assert.True(t, fltrs[i].invoked) {
1040 t.FailNow()
1041 }
1042 }
1043
1044 if !assert.EqualError(t, err, "message: 1\n---\nmessage: 2") {
1045 t.FailNow()
1046 }
1047 b, err := os.ReadFile(
1048 filepath.Join(dir, "java", "java-deployment.resource.yaml"))
1049 if !assert.NoError(t, err) {
1050 t.FailNow()
1051 }
1052
1053 assert.Contains(t, string(b), "kind: Deployment")
1054 }
1055
1056
1057 func TestCmd_Execute_setFunctionPaths(t *testing.T) {
1058 dir := setupTest(t)
1059
1060
1061 tmpF, err := os.CreateTemp("", "filter*.yaml")
1062 if !assert.NoError(t, err) {
1063 return
1064 }
1065 os.RemoveAll(tmpF.Name())
1066 if !assert.NoError(t, os.WriteFile(tmpF.Name(), []byte(ValueReplacerYAMLData), 0600)) {
1067 return
1068 }
1069
1070
1071 instance := RunFns{
1072 FunctionPaths: []string{tmpF.Name()},
1073 Path: dir,
1074 functionFilterProvider: getFilterProvider(t),
1075 }
1076
1077 instance.init()
1078
1079 err = instance.Execute()
1080 if !assert.NoError(t, err) {
1081 return
1082 }
1083 b, err := os.ReadFile(
1084 filepath.Join(dir, "java", "java-deployment.resource.yaml"))
1085 if !assert.NoError(t, err) {
1086 return
1087 }
1088 assert.Contains(t, string(b), "kind: StatefulSet")
1089 }
1090
1091
1092 func TestCmd_Execute_setOutput(t *testing.T) {
1093 dir := setupTest(t)
1094
1095
1096 if !assert.NoError(t, os.WriteFile(
1097 filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
1098 return
1099 }
1100
1101 out := &bytes.Buffer{}
1102 instance := RunFns{
1103 Output: out,
1104 Path: dir,
1105 functionFilterProvider: getFilterProvider(t),
1106 }
1107
1108 instance.init()
1109
1110 if !assert.NoError(t, instance.Execute()) {
1111 return
1112 }
1113 b, err := os.ReadFile(
1114 filepath.Join(dir, "java", "java-deployment.resource.yaml"))
1115 if !assert.NoError(t, err) {
1116 return
1117 }
1118 assert.NotContains(t, string(b), "kind: StatefulSet")
1119 assert.Contains(t, out.String(), "kind: StatefulSet")
1120 }
1121
1122
1123 func TestCmd_Execute_setInput(t *testing.T) {
1124 dir := setupTest(t)
1125 if !assert.NoError(t, os.WriteFile(
1126 filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
1127 return
1128 }
1129
1130 read, err := kio.LocalPackageReader{PackagePath: dir}.Read()
1131 if !assert.NoError(t, err) {
1132 t.FailNow()
1133 }
1134 input := &bytes.Buffer{}
1135 if !assert.NoError(t, kio.ByteWriter{Writer: input}.Write(read)) {
1136 t.FailNow()
1137 }
1138
1139 outDir := t.TempDir()
1140
1141 if !assert.NoError(t, os.WriteFile(
1142 filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
1143 return
1144 }
1145
1146 instance := RunFns{
1147 Input: input,
1148 Path: outDir,
1149 functionFilterProvider: getFilterProvider(t),
1150 }
1151
1152 instance.init()
1153
1154 if !assert.NoError(t, instance.Execute()) {
1155 return
1156 }
1157 b, err := os.ReadFile(
1158 filepath.Join(outDir, "java", "java-deployment.resource.yaml"))
1159 if !assert.NoError(t, err) {
1160 t.FailNow()
1161 }
1162 assert.Contains(t, string(b), "kind: StatefulSet")
1163 }
1164
1165
1166 func TestCmd_Execute_enableLogSteps(t *testing.T) {
1167 dir := setupTest(t)
1168
1169
1170 if !assert.NoError(t, os.WriteFile(
1171 filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
1172 return
1173 }
1174
1175 logs := &bytes.Buffer{}
1176 instance := RunFns{
1177 Path: dir,
1178 functionFilterProvider: getFilterProvider(t),
1179 LogSteps: true,
1180 LogWriter: logs,
1181 }
1182 if !assert.NoError(t, instance.Execute()) {
1183 t.FailNow()
1184 }
1185 b, err := os.ReadFile(
1186 filepath.Join(dir, "java", "java-deployment.resource.yaml"))
1187 if !assert.NoError(t, err) {
1188 t.FailNow()
1189 }
1190 assert.Contains(t, string(b), "kind: StatefulSet")
1191 assert.Equal(t, "Running unknown-type function\n", logs.String())
1192 }
1193
1194 func getGeneratorFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) {
1195 t.Helper()
1196 return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
1197 return kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
1198 if f.Container.Image == "generate" {
1199 node, err := yaml.Parse("kind: generated")
1200 if !assert.NoError(t, err) {
1201 t.FailNow()
1202 }
1203 return append(items, node), nil
1204 }
1205 return items, nil
1206 }), nil
1207 }
1208 }
1209 func TestRunFns_ContinueOnEmptyResult(t *testing.T) {
1210 fn1, err := yaml.Parse(`
1211 kind: fakefn
1212 metadata:
1213 annotations:
1214 config.kubernetes.io/function: |
1215 container:
1216 image: pass
1217 `)
1218 if !assert.NoError(t, err) {
1219 t.FailNow()
1220 }
1221 fn2, err := yaml.Parse(`
1222 kind: fakefn
1223 metadata:
1224 annotations:
1225 config.kubernetes.io/function: |
1226 container:
1227 image: generate
1228 `)
1229 if !assert.NoError(t, err) {
1230 t.FailNow()
1231 }
1232
1233 var test = []struct {
1234 ContinueOnEmptyResult bool
1235 ExpectedOutput string
1236 }{
1237 {
1238 ContinueOnEmptyResult: false,
1239 ExpectedOutput: "",
1240 },
1241 {
1242 ContinueOnEmptyResult: true,
1243 ExpectedOutput: "kind: generated\n",
1244 },
1245 }
1246 for i := range test {
1247 ouputBuffer := bytes.Buffer{}
1248 instance := RunFns{
1249 Input: bytes.NewReader([]byte{}),
1250 Output: &ouputBuffer,
1251 Functions: []*yaml.RNode{fn1, fn2},
1252 functionFilterProvider: getGeneratorFilterProvider(t),
1253 ContinueOnEmptyResult: test[i].ContinueOnEmptyResult,
1254 }
1255 if !assert.NoError(t, instance.Execute()) {
1256 t.FailNow()
1257 }
1258 assert.Equal(t, test[i].ExpectedOutput, ouputBuffer.String())
1259 }
1260 }
1261
1262
1263 func setupTest(t *testing.T) string {
1264 t.Helper()
1265 dir := t.TempDir()
1266
1267 _, filename, _, ok := runtime.Caller(0)
1268 if !assert.True(t, ok) {
1269 t.FailNow()
1270 }
1271 ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
1272 if !assert.NoError(t, err) {
1273 t.FailNow()
1274 }
1275 if !assert.NoError(t, copyutil.CopyDir(filesys.MakeFsOnDisk(), ds, dir)) {
1276 t.FailNow()
1277 }
1278
1279 cwd, err := os.Getwd()
1280 if !assert.NoError(t, err) {
1281 t.FailNow()
1282 }
1283
1284 if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
1285 t.FailNow()
1286 }
1287
1288
1289 t.Cleanup(func() {
1290 if !assert.NoError(t, os.Chdir(cwd)) {
1291 t.FailNow()
1292 }
1293 })
1294
1295 return dir
1296 }
1297
1298
1299
1300
1301 func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) {
1302 t.Helper()
1303 return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
1304
1305 filter := yaml.YFilter{}
1306 b := &bytes.Buffer{}
1307 e := yaml.NewEncoder(b)
1308 if !assert.NoError(t, e.Encode(node.YNode())) {
1309 t.FailNow()
1310 }
1311 e.Close()
1312 d := yaml.NewDecoder(b)
1313 if !assert.NoError(t, d.Decode(&filter)) {
1314 t.FailNow()
1315 }
1316
1317 return filters.Modifier{
1318 Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
1319 }, nil
1320 }
1321 }
1322
1323 func TestRunFns_mergeContainerEnv(t *testing.T) {
1324 testcases := []struct {
1325 name string
1326 instance RunFns
1327 inputEnvs []string
1328 expect runtimeutil.ContainerEnv
1329 }{
1330 {
1331 name: "all empty",
1332 instance: RunFns{},
1333 expect: *runtimeutil.NewContainerEnv(),
1334 },
1335 {
1336 name: "empty command line envs",
1337 instance: RunFns{},
1338 inputEnvs: []string{"foo=bar"},
1339 expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar"}),
1340 },
1341 {
1342 name: "empty declarative envs",
1343 instance: RunFns{
1344 Env: []string{"foo=bar"},
1345 },
1346 expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar"}),
1347 },
1348 {
1349 name: "same key",
1350 instance: RunFns{
1351 Env: []string{"foo=bar", "foo"},
1352 },
1353 inputEnvs: []string{"foo=bar1", "bar"},
1354 expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar", "bar", "foo"}),
1355 },
1356 {
1357 name: "same exported key",
1358 instance: RunFns{
1359 Env: []string{"foo=bar", "foo"},
1360 },
1361 inputEnvs: []string{"foo1=bar1", "foo"},
1362 expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar", "foo1=bar1", "foo"}),
1363 },
1364 }
1365
1366 for i := range testcases {
1367 tc := testcases[i]
1368 t.Run(tc.name, func(t *testing.T) {
1369 envs := tc.instance.mergeContainerEnv(tc.inputEnvs)
1370 assert.Equal(t, tc.expect.GetDockerFlags(), runtimeutil.NewContainerEnvFromStringSlice(envs).GetDockerFlags())
1371 })
1372 }
1373 }
1374
View as plain text