1
2
3
4 package framework_test
5
6 import (
7 "bytes"
8 "fmt"
9 "log"
10 "path/filepath"
11 "strings"
12
13 validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
14 "k8s.io/kube-openapi/pkg/validation/spec"
15 "sigs.k8s.io/kustomize/kyaml/errors"
16 "sigs.k8s.io/kustomize/kyaml/fn/framework"
17 "sigs.k8s.io/kustomize/kyaml/fn/framework/command"
18 "sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
19 "sigs.k8s.io/kustomize/kyaml/kio"
20 "sigs.k8s.io/kustomize/kyaml/resid"
21 "sigs.k8s.io/kustomize/kyaml/yaml"
22 )
23
24 const service = "Service"
25
26
27 func ExampleSimpleProcessor_modify() {
28 input := bytes.NewBufferString(`
29 apiVersion: config.kubernetes.io/v1
30 kind: ResourceList
31 # items are provided as nodes
32 items:
33 - apiVersion: apps/v1
34 kind: Deployment
35 metadata:
36 name: foo
37 - apiVersion: v1
38 kind: Service
39 metadata:
40 name: foo
41 functionConfig:
42 apiVersion: v1
43 kind: ConfigMap
44 data:
45 value: baz
46 `)
47 config := new(struct {
48 Data map[string]string `yaml:"data" json:"data"`
49 })
50 fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
51 for i := range items {
52
53 if err := items[i].PipeE(yaml.SetAnnotation("value", config.Data["value"])); err != nil {
54 return nil, err
55 }
56 }
57 return items, nil
58 }
59
60 err := framework.Execute(framework.SimpleProcessor{Config: config, Filter: kio.FilterFunc(fn)}, &kio.ByteReadWriter{Reader: input})
61 if err != nil {
62 panic(err)
63 }
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 }
87
88
89
90 func ExampleSimpleProcessor_generateReplace() {
91 input := bytes.NewBufferString(`
92 apiVersion: config.kubernetes.io/v1
93 kind: ResourceList
94 # items are provided as nodes
95 items:
96 - apiVersion: apps/v1
97 kind: Deployment
98 metadata:
99 name: foo
100 functionConfig:
101 apiVersion: example.com/v1alpha1
102 kind: ExampleServiceGenerator
103 spec:
104 name: bar
105 `)
106
107
108
109 type Spec struct {
110 Name string `yaml:"name,omitempty"`
111 }
112 type ExampleServiceGenerator struct {
113 Spec Spec `yaml:"spec,omitempty"`
114 }
115
116 functionConfig := &ExampleServiceGenerator{}
117
118 fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
119
120 var newNodes []*yaml.RNode
121 for i := range items {
122 meta, err := items[i].GetMeta()
123 if err != nil {
124 return nil, err
125 }
126
127 if meta.Name == functionConfig.Spec.Name &&
128 meta.Kind == service &&
129 meta.APIVersion == "v1" {
130 continue
131 }
132 newNodes = append(newNodes, items[i])
133 }
134 items = newNodes
135
136
137 n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
138 kind: Service
139 metadata:
140 name: %s
141 `, functionConfig.Spec.Name))
142 if err != nil {
143 return nil, err
144 }
145 items = append(items, n)
146 return items, nil
147 }
148
149 p := framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
150 err := framework.Execute(p, &kio.ByteReadWriter{Reader: input})
151 if err != nil {
152 panic(err)
153 }
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 }
173
174
175
176 func ExampleTemplateProcessor_generate_inline() {
177 api := new(struct {
178 Key string `json:"key" yaml:"key"`
179 Value string `json:"value" yaml:"value"`
180 })
181
182 fn := framework.TemplateProcessor{
183
184 TemplateData: api,
185
186 ResourceTemplates: []framework.ResourceTemplate{{
187 Templates: parser.TemplateStrings(`
188 apiVersion: apps/v1
189 kind: Deployment
190 metadata:
191 name: foo
192 namespace: default
193 annotations:
194 {{ .Key }}: {{ .Value }}
195 `)}},
196 }
197 cmd := command.Build(fn, command.StandaloneEnabled, false)
198
199
200 cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
201 if err := cmd.Execute(); err != nil {
202 _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
203 }
204
205
206
207
208
209
210
211
212
213 }
214
215
216
217 func ExampleTemplateProcessor_generate_files() {
218 api := new(struct {
219 Key string `json:"key" yaml:"key"`
220 Value string `json:"value" yaml:"value"`
221 })
222
223 templateFn := framework.TemplateProcessor{
224
225 TemplateData: api,
226
227 ResourceTemplates: []framework.ResourceTemplate{{
228 Templates: parser.TemplateFiles("testdata/example/templatefiles/deployment.template.yaml"),
229 }},
230 }
231 cmd := command.Build(templateFn, command.StandaloneEnabled, false)
232
233 cmd.SetArgs([]string{filepath.Join("testdata", "example", "templatefiles", "config.yaml")})
234 if err := cmd.Execute(); err != nil {
235 _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 }
250
251
252
253 func ExampleTemplateProcessor_preprocess() {
254 config := new(struct {
255 Key string `json:"key" yaml:"key"`
256 Value string `json:"value" yaml:"value"`
257 Short bool
258 })
259
260
261 fn := framework.TemplateProcessor{
262
263 TemplateData: config,
264 PreProcessFilters: []kio.Filter{
265 kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
266 config.Short = len(items) < 3
267 return items, nil
268 }),
269 },
270
271 ResourceTemplates: []framework.ResourceTemplate{{
272 Templates: parser.TemplateStrings(`
273 apiVersion: apps/v1
274 kind: Deployment
275 metadata:
276 name: foo
277 namespace: default
278 annotations:
279 {{ .Key }}: {{ .Value }}
280 {{- if .Short }}
281 short: 'true'
282 {{- end }}
283 ---
284 apiVersion: apps/v1
285 kind: Deployment
286 metadata:
287 name: bar
288 namespace: default
289 annotations:
290 {{ .Key }}: {{ .Value }}
291 {{- if .Short }}
292 short: 'true'
293 {{- end }}
294 `),
295 }},
296 }
297
298 cmd := command.Build(fn, command.StandaloneEnabled, false)
299
300 cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
301 if err := cmd.Execute(); err != nil {
302 _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323 }
324
325
326
327 func ExampleTemplateProcessor_postprocess() {
328 config := new(struct {
329 Key string `json:"key" yaml:"key"`
330 Value string `json:"value" yaml:"value"`
331 })
332
333
334 fn := framework.TemplateProcessor{
335
336 TemplateData: config,
337 ResourceTemplates: []framework.ResourceTemplate{{
338 Templates: parser.TemplateStrings(`
339 apiVersion: apps/v1
340 kind: Deployment
341 metadata:
342 name: foo
343 namespace: default
344 annotations:
345 {{ .Key }}: {{ .Value }}
346 ---
347 apiVersion: apps/v1
348 kind: Deployment
349 metadata:
350 name: bar
351 namespace: default
352 annotations:
353 {{ .Key }}: {{ .Value }}
354 `),
355 }},
356 PostProcessFilters: []kio.Filter{
357 kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
358 items = items[1:]
359 return items, nil
360 }),
361 },
362 }
363 cmd := command.Build(fn, command.StandaloneEnabled, false)
364
365 cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
366 if err := cmd.Execute(); err != nil {
367 _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
368 }
369
370
371
372
373
374
375
376
377
378 }
379
380
381
382 func ExampleTemplateProcessor_patch() {
383 fn := framework.TemplateProcessor{
384 TemplateData: new(struct {
385 Key string `json:"key" yaml:"key"`
386 Value string `json:"value" yaml:"value"`
387 }),
388 ResourceTemplates: []framework.ResourceTemplate{{
389 Templates: parser.TemplateStrings(`
390 apiVersion: apps/v1
391 kind: Deployment
392 metadata:
393 name: foo
394 namespace: default
395 annotations:
396 {{ .Key }}: {{ .Value }}
397 ---
398 apiVersion: apps/v1
399 kind: Deployment
400 metadata:
401 name: bar
402 namespace: default
403 annotations:
404 {{ .Key }}: {{ .Value }}
405 `),
406 }},
407
408 PatchTemplates: []framework.PatchTemplate{
409 &framework.ResourcePatchTemplate{
410
411 Selector: &framework.Selector{Names: []string{"foo"}},
412 Templates: parser.TemplateStrings(`
413 metadata:
414 annotations:
415 patched: 'true'
416 `),
417 }},
418 }
419 cmd := command.Build(fn, command.StandaloneEnabled, false)
420
421 cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
422 if err := cmd.Execute(); err != nil {
423 _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
424 }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443 }
444
445
446
447
448 func ExampleTemplateProcessor_MergeResources() {
449 p := framework.TemplateProcessor{
450 TemplateData: new(struct {
451 Name string `json:"name" yaml:"name"`
452 }),
453 ResourceTemplates: []framework.ResourceTemplate{{
454
455 Templates: parser.TemplateStrings(`
456 apiVersion: apps/v1
457 kind: Deployment
458 metadata:
459 name: {{ .Name }}
460 spec:
461 replicas: 1
462 selector:
463 app: foo
464 template:
465 spec:
466 containers:
467 - name: app
468 image: example.io/team/app
469 `),
470 }},
471 MergeResources: true,
472 }
473
474
475
476 rw := kio.ByteReadWriter{Reader: bytes.NewBufferString(`
477 apiVersion: config.kubernetes.io/v1
478 kind: ResourceList
479 items:
480 - kind: Deployment
481 apiVersion: apps/v1
482 metadata:
483 name: custom
484 spec:
485 replicas: 6
486 selector:
487 app: custom
488 template:
489 spec:
490 containers:
491 - name: app
492 image: example.io/team/custom
493 - kind: Deployment
494 apiVersion: apps/v1
495 metadata:
496 name: mergeTest
497 spec:
498 replicas: 6
499 functionConfig:
500 name: mergeTest
501 `)}
502 if err := framework.Execute(p, &rw); err != nil {
503 panic(err)
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538 }
539
540
541
542
543 func ExampleSelector_templatizeKinds() {
544 type api struct {
545 KindName string `yaml:"kindName"`
546 }
547 rw := &kio.ByteReadWriter{
548 Reader: bytes.NewBufferString(`
549 apiVersion: config.kubernetes.io/v1
550 kind: ResourceList
551 functionConfig:
552 kindName: Deployment
553 items:
554 - apiVersion: apps/v1
555 kind: Deployment
556 metadata:
557 name: foo
558 namespace: default
559 - apiVersion: apps/v1
560 kind: StatefulSet
561 metadata:
562 name: bar
563 namespace: default
564 `),
565 }
566 config := &api{}
567 p := framework.SimpleProcessor{
568 Config: config,
569 Filter: &framework.Selector{
570 TemplateData: config,
571 Kinds: []string{"{{ .KindName }}"},
572 },
573 }
574
575 err := framework.Execute(p, rw)
576 if err != nil {
577 panic(err)
578 }
579
580
581
582
583
584
585
586
587
588
589
590
591 }
592
593
594
595
596 func ExampleSelector_templatizeAnnotations() {
597 type api struct {
598 Value string `yaml:"value"`
599 }
600 rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
601 apiVersion: config.kubernetes.io/v1
602 kind: ResourceList
603 functionConfig:
604 value: bar
605 items:
606 - apiVersion: apps/v1
607 kind: Deployment
608 metadata:
609 name: foo
610 namespace: default
611 annotations:
612 key: foo
613 - apiVersion: apps/v1
614 kind: Deployment
615 metadata:
616 name: bar
617 namespace: default
618 annotations:
619 key: bar
620 `)}
621 config := &api{}
622 p := framework.SimpleProcessor{
623 Config: config,
624 Filter: &framework.Selector{
625 TemplateData: config,
626 Annotations: map[string]string{"key": "{{ .Value }}"},
627 },
628 }
629
630 if err := framework.Execute(p, rw); err != nil {
631 panic(err)
632 }
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647 }
648
649
650
651 func ExampleTemplateProcessor_container_patch() {
652 input := `
653 apiVersion: apps/v1
654 kind: Deployment
655 metadata:
656 name: foo
657 spec:
658 template:
659 spec:
660 containers:
661 - name: foo
662 image: a
663 - name: bar
664 image: b
665 ---
666 apiVersion: v1
667 kind: Service
668 metadata:
669 name: foo
670 spec:
671 selector:
672 foo: bar
673 ---
674 apiVersion: apps/v1
675 kind: Deployment
676 metadata:
677 name: bar
678 spec:
679 template:
680 spec:
681 containers:
682 - name: foo
683 image: a
684 - name: baz
685 image: b
686 ---
687 apiVersion: v1
688 kind: Service
689 metadata:
690 name: bar
691 spec:
692 selector:
693 foo: bar
694 `
695 p := framework.TemplateProcessor{
696 PatchTemplates: []framework.PatchTemplate{
697 &framework.ContainerPatchTemplate{
698 Templates: parser.TemplateStrings(`
699 env:
700 - name: KEY
701 value: {{ .Value }}
702 `),
703 TemplateData: struct{ Value string }{Value: "new-value"},
704 }},
705 }
706 err := framework.Execute(p, &kio.ByteReadWriter{Reader: bytes.NewBufferString(input)})
707 if err != nil {
708 log.Fatal(err)
709 }
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765 }
766
767
768
769 func ExampleTemplateProcessor_container_patch_by_name() {
770 input := `
771 apiVersion: apps/v1
772 kind: Deployment
773 metadata:
774 name: foo
775 spec:
776 template:
777 spec:
778 containers:
779 - name: foo
780 image: a
781 env:
782 - name: EXISTING
783 value: variable
784 - name: bar
785 image: b
786 ---
787 apiVersion: v1
788 kind: Service
789 metadata:
790 name: foo
791 spec:
792 selector:
793 foo: bar
794 ---
795 apiVersion: apps/v1
796 kind: Deployment
797 metadata:
798 name: bar
799 spec:
800 template:
801 spec:
802 containers:
803 - name: foo
804 image: a
805 - name: baz
806 image: b
807 ---
808 apiVersion: v1
809 kind: Service
810 metadata:
811 name: bar
812 spec:
813 selector:
814 foo: bar
815 `
816 p := framework.TemplateProcessor{
817 TemplateData: struct{ Value string }{Value: "new-value"},
818 PatchTemplates: []framework.PatchTemplate{
819 &framework.ContainerPatchTemplate{
820
821 ContainerMatcher: framework.ContainerNameMatcher("foo"),
822 Templates: parser.TemplateStrings(`
823 env:
824 - name: KEY
825 value: {{ .Value }}
826 `),
827 }},
828 }
829
830 err := framework.Execute(p, &kio.ByteReadWriter{Reader: bytes.NewBufferString(input)})
831 if err != nil {
832 log.Fatal(err)
833 }
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885 }
886
887 type v1alpha1JavaSpringBoot struct {
888 Metadata Metadata `yaml:"metadata" json:"metadata"`
889 Spec v1alpha1JavaSpringBootSpec `yaml:"spec" json:"spec"`
890 }
891
892 type Metadata struct {
893 Name string `yaml:"name" json:"name"`
894 }
895
896 type v1alpha1JavaSpringBootSpec struct {
897 Replicas int `yaml:"replicas" json:"replicas"`
898 Domain string `yaml:"domain" json:"domain"`
899 Image string `yaml:"image" json:"image"`
900 }
901
902 func (a v1alpha1JavaSpringBoot) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
903 filter := framework.TemplateProcessor{
904 ResourceTemplates: []framework.ResourceTemplate{{
905 TemplateData: &a,
906 Templates: parser.TemplateStrings(`
907 apiVersion: apps/v1
908 kind: Deployment
909 metadata:
910 name: {{ .Metadata.Name }}
911 selector:
912 app: {{ .Metadata.Name }}
913 spec:
914 replicas: {{ .Spec.Replicas }}
915 template:
916 spec:
917 containers:
918 - name: app
919 image: {{ .Spec.Image }}
920 {{ if .Spec.Domain }}
921 ports:
922 - containerPort: 80
923 name: http
924 {{ end }}
925
926 {{ if .Spec.Domain }}
927 ---
928 apiVersion: v1
929 kind: Service
930 metadata:
931 name: {{ .Metadata.Name }}-svc
932 spec:
933 selector:
934 app: {{ .Metadata.Name }}
935 ports:
936 - protocol: TCP
937 port: 80
938 targetPort: 80
939 ---
940 apiVersion: networking.k8s.io/v1
941 kind: Ingress
942 metadata:
943 name: {{ .Metadata.Name }}-ingress
944 spec:
945 tls:
946 - hosts:
947 - {{ .Spec.Domain }}
948 secretName: secret-tls
949 defaultBackend:
950 service:
951 name: {{ .Metadata.Name }}
952 port:
953 number: 80
954 {{ end }}
955 `),
956 }},
957 }
958 return filter.Filter(items)
959 }
960
961 func (a *v1alpha1JavaSpringBoot) Default() error {
962 if a.Spec.Replicas == 0 {
963 a.Spec.Replicas = 3
964 }
965 return nil
966 }
967
968 var javaSpringBootDefinition = `
969 apiVersion: config.kubernetes.io/v1alpha1
970 kind: KRMFunctionDefinition
971 metadata:
972 name: javaspringboot.example.com
973 spec:
974 group: example.com
975 names:
976 kind: JavaSpringBoot
977 versions:
978 - name: v1alpha1
979 schema:
980 openAPIV3Schema:
981 properties:
982 apiVersion:
983 type: string
984 kind:
985 type: string
986 metadata:
987 type: object
988 properties:
989 name:
990 type: string
991 minLength: 1
992 required:
993 - name
994 spec:
995 properties:
996 domain:
997 pattern: example\.com$
998 type: string
999 image:
1000 type: string
1001 replicas:
1002 maximum: 9
1003 minimum: 0
1004 type: integer
1005 type: object
1006 type: object
1007 `
1008
1009 func (a v1alpha1JavaSpringBoot) Schema() (*spec.Schema, error) {
1010 schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk("example.com", "v1alpha1", "JavaSpringBoot"), javaSpringBootDefinition)
1011 return schema, errors.WrapPrefixf(err, "parsing JavaSpringBoot schema")
1012 }
1013
1014 func (a *v1alpha1JavaSpringBoot) Validate() error {
1015 var errs []error
1016 if strings.HasSuffix(a.Spec.Image, ":latest") {
1017 errs = append(errs, errors.Errorf("spec.image should not have latest tag"))
1018 }
1019 if len(errs) > 0 {
1020 return validationErrors.CompositeValidationError(errs...)
1021 }
1022 return nil
1023 }
1024
1025
1026
1027 func ExampleVersionedAPIProcessor() {
1028 p := &framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{
1029 "JavaSpringBoot": {
1030 "example.com/v1alpha1": &v1alpha1JavaSpringBoot{},
1031 }}}
1032
1033 source := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
1034 apiVersion: config.kubernetes.io/v1
1035 kind: ResourceList
1036 functionConfig:
1037 apiVersion: example.com/v1alpha1
1038 kind: JavaSpringBoot
1039 metadata:
1040 name: my-app
1041 spec:
1042 image: example.docker.com/team/app:1.0
1043 domain: demo.example.com
1044 `)}
1045 if err := framework.Execute(p, source); err != nil {
1046 log.Fatal(err)
1047 }
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102 }
1103
View as plain text