...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/framework/example_test.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/framework

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     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  // ExampleSimpleProcessor_modify implements a function that sets an annotation on each resource.
    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  			// set the annotation on each resource item
    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  	// Output:
    66  	// apiVersion: config.kubernetes.io/v1
    67  	// kind: ResourceList
    68  	// items:
    69  	// - apiVersion: apps/v1
    70  	//   kind: Deployment
    71  	//   metadata:
    72  	//     name: foo
    73  	//     annotations:
    74  	//       value: 'baz'
    75  	// - apiVersion: v1
    76  	//   kind: Service
    77  	//   metadata:
    78  	//     name: foo
    79  	//     annotations:
    80  	//       value: 'baz'
    81  	// functionConfig:
    82  	//   apiVersion: v1
    83  	//   kind: ConfigMap
    84  	//   data:
    85  	//     value: baz
    86  }
    87  
    88  // ExampleSimpleProcessor_generateReplace generates a resource from a FunctionConfig.
    89  // If the resource already exists, it replaces the resource with a new copy.
    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  	// function API definition which will be parsed from the ResourceList.FunctionConfig
   108  	// read from stdin
   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  		// remove the last generated resource
   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  			// something we already generated, remove it from the list so we regenerate it
   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  		// generate the resource again
   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  	// Output:
   156  	// apiVersion: config.kubernetes.io/v1
   157  	// kind: ResourceList
   158  	// items:
   159  	// - apiVersion: apps/v1
   160  	//   kind: Deployment
   161  	//   metadata:
   162  	//     name: foo
   163  	// - apiVersion: v1
   164  	//   kind: Service
   165  	//   metadata:
   166  	//     name: bar
   167  	// functionConfig:
   168  	//   apiVersion: example.com/v1alpha1
   169  	//   kind: ExampleServiceGenerator
   170  	//   spec:
   171  	//     name: bar
   172  }
   173  
   174  // ExampleTemplateProcessor provides an example for using the TemplateProcessor to add resources
   175  // from templates defined inline
   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  	// create the template
   182  	fn := framework.TemplateProcessor{
   183  		// Templates input
   184  		TemplateData: api,
   185  		// Templates
   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  	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
   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  	// Output:
   206  	// apiVersion: apps/v1
   207  	// kind: Deployment
   208  	// metadata:
   209  	//   name: foo
   210  	//   namespace: default
   211  	//   annotations:
   212  	//     a: b
   213  }
   214  
   215  // ExampleTemplateProcessor_files provides an example for using the TemplateProcessor to add
   216  // resources from templates defined in files.
   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  	// create the template
   223  	templateFn := framework.TemplateProcessor{
   224  		// Templates input
   225  		TemplateData: api,
   226  		// Templates
   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  	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
   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  	// Output:
   239  	// # Copyright 2021 The Kubernetes Authors.
   240  	// # SPDX-License-Identifier: Apache-2.0
   241  	//
   242  	// apiVersion: apps/v1
   243  	// kind: Deployment
   244  	// metadata:
   245  	//   name: foo
   246  	//   namespace: default
   247  	//   annotations:
   248  	//     a: b
   249  }
   250  
   251  // ExampleTemplateProcessor_preprocess provides an example for using the TemplateProcessor
   252  // with PreProcess to configure the template based on the input resources observed.
   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  	// create the template
   261  	fn := framework.TemplateProcessor{
   262  		// Templates input
   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  		// Templates
   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  	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
   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  	// Output:
   306  	// apiVersion: apps/v1
   307  	// kind: Deployment
   308  	// metadata:
   309  	//   name: foo
   310  	//   namespace: default
   311  	//   annotations:
   312  	//     a: b
   313  	//     short: 'true'
   314  	// ---
   315  	// apiVersion: apps/v1
   316  	// kind: Deployment
   317  	// metadata:
   318  	//   name: bar
   319  	//   namespace: default
   320  	//   annotations:
   321  	//     a: b
   322  	//     short: 'true'
   323  }
   324  
   325  // ExampleTemplateProcessor_postprocess provides an example for using the TemplateProcessor
   326  // with PostProcess to modify the results.
   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  	// create the template
   334  	fn := framework.TemplateProcessor{
   335  		// Templates input
   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  	// Output:
   371  	// apiVersion: apps/v1
   372  	// kind: Deployment
   373  	// metadata:
   374  	//   name: bar
   375  	//   namespace: default
   376  	//   annotations:
   377  	//     a: b
   378  }
   379  
   380  // ExampleTemplateProcessor_patch provides an example for using the TemplateProcessor to
   381  // create a function that patches resources.
   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  		// PatchTemplates are applied to BOTH ResourceList input resources AND templated resources
   408  		PatchTemplates: []framework.PatchTemplate{
   409  			&framework.ResourcePatchTemplate{
   410  				// patch the foo resource only
   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  	// Output:
   427  	// apiVersion: apps/v1
   428  	// kind: Deployment
   429  	// metadata:
   430  	//   name: foo
   431  	//   namespace: default
   432  	//   annotations:
   433  	//     a: b
   434  	//     patched: 'true'
   435  	// ---
   436  	// apiVersion: apps/v1
   437  	// kind: Deployment
   438  	// metadata:
   439  	//   name: bar
   440  	//   namespace: default
   441  	//   annotations:
   442  	//     a: b
   443  }
   444  
   445  // ExampleTemplateProcessor_MergeResources provides an example for using the TemplateProcessor to
   446  // create a function that treats incoming resources as potential patches
   447  // for the resources the function generates itself.
   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  			// This is the generated resource the input will patch
   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  	// The second resource will be treated as a patch since its metadata matches the resource
   475  	// generated by ResourceTemplates and MergeResources is true.
   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  	// Output:
   507  	// apiVersion: config.kubernetes.io/v1
   508  	// kind: ResourceList
   509  	// items:
   510  	// - apiVersion: apps/v1
   511  	//   kind: Deployment
   512  	//   metadata:
   513  	//     name: mergeTest
   514  	//   spec:
   515  	//     replicas: 6
   516  	//     selector:
   517  	//       app: foo
   518  	//     template:
   519  	//       spec:
   520  	//         containers:
   521  	//         - name: app
   522  	//           image: example.io/team/app
   523  	// - kind: Deployment
   524  	//   apiVersion: apps/v1
   525  	//   metadata:
   526  	//     name: custom
   527  	//   spec:
   528  	//     replicas: 6
   529  	//   selector:
   530  	//     app: custom
   531  	//   template:
   532  	//     spec:
   533  	//       containers:
   534  	//       - name: app
   535  	//         image: example.io/team/custom
   536  	// functionConfig:
   537  	//   name: mergeTest
   538  }
   539  
   540  // ExampleSelector_templatizeKinds provides an example of using a template as a selector value,
   541  // to dynamically match resources based on the functionConfig input. It also shows how Selector
   542  // can be used with SimpleProcessor to implement a ResourceListProcessor the filters the input.
   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  	// Output:
   581  	// apiVersion: config.kubernetes.io/v1
   582  	// kind: ResourceList
   583  	// items:
   584  	// - apiVersion: apps/v1
   585  	//   kind: Deployment
   586  	//   metadata:
   587  	//     name: foo
   588  	//     namespace: default
   589  	// functionConfig:
   590  	//   kindName: Deployment
   591  }
   592  
   593  // ExampleSelector_templatizeKinds provides an example of using a template as a selector value,
   594  // to dynamically match resources based on the functionConfig input. It also shows how Selector
   595  // can be used with SimpleProcessor to implement a ResourceListProcessor the filters the input.
   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  	// Output:
   635  	// apiVersion: config.kubernetes.io/v1
   636  	// kind: ResourceList
   637  	// items:
   638  	// - apiVersion: apps/v1
   639  	//   kind: Deployment
   640  	//   metadata:
   641  	//     name: bar
   642  	//     namespace: default
   643  	//     annotations:
   644  	//       key: bar
   645  	// functionConfig:
   646  	//   value: bar
   647  }
   648  
   649  // ExampleTemplateProcessor_container_patch provides an example for using TemplateProcessor to
   650  // patch all of the containers in the input.
   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  	// Output:
   712  	// apiVersion: apps/v1
   713  	// kind: Deployment
   714  	// metadata:
   715  	//   name: foo
   716  	// spec:
   717  	//   template:
   718  	//     spec:
   719  	//       containers:
   720  	//       - name: foo
   721  	//         image: a
   722  	//         env:
   723  	//         - name: KEY
   724  	//           value: new-value
   725  	//       - name: bar
   726  	//         image: b
   727  	//         env:
   728  	//         - name: KEY
   729  	//           value: new-value
   730  	// ---
   731  	// apiVersion: v1
   732  	// kind: Service
   733  	// metadata:
   734  	//   name: foo
   735  	// spec:
   736  	//   selector:
   737  	//     foo: bar
   738  	// ---
   739  	// apiVersion: apps/v1
   740  	// kind: Deployment
   741  	// metadata:
   742  	//   name: bar
   743  	// spec:
   744  	//   template:
   745  	//     spec:
   746  	//       containers:
   747  	//       - name: foo
   748  	//         image: a
   749  	//         env:
   750  	//         - name: KEY
   751  	//           value: new-value
   752  	//       - name: baz
   753  	//         image: b
   754  	//         env:
   755  	//         - name: KEY
   756  	//           value: new-value
   757  	// ---
   758  	// apiVersion: v1
   759  	// kind: Service
   760  	// metadata:
   761  	//   name: bar
   762  	// spec:
   763  	//   selector:
   764  	//     foo: bar
   765  }
   766  
   767  // PatchTemplateContainersWithString patches containers matching
   768  // a specific name.
   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  				// Only patch containers named "foo"
   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  	// Output:
   836  	// apiVersion: apps/v1
   837  	// kind: Deployment
   838  	// metadata:
   839  	//   name: foo
   840  	// spec:
   841  	//   template:
   842  	//     spec:
   843  	//       containers:
   844  	//       - name: foo
   845  	//         image: a
   846  	//         env:
   847  	//         - name: EXISTING
   848  	//           value: variable
   849  	//         - name: KEY
   850  	//           value: new-value
   851  	//       - name: bar
   852  	//         image: b
   853  	// ---
   854  	// apiVersion: v1
   855  	// kind: Service
   856  	// metadata:
   857  	//   name: foo
   858  	// spec:
   859  	//   selector:
   860  	//     foo: bar
   861  	// ---
   862  	// apiVersion: apps/v1
   863  	// kind: Deployment
   864  	// metadata:
   865  	//   name: bar
   866  	// spec:
   867  	//   template:
   868  	//     spec:
   869  	//       containers:
   870  	//       - name: foo
   871  	//         image: a
   872  	//         env:
   873  	//         - name: KEY
   874  	//           value: new-value
   875  	//       - name: baz
   876  	//         image: b
   877  	// ---
   878  	// apiVersion: v1
   879  	// kind: Service
   880  	// metadata:
   881  	//   name: bar
   882  	// spec:
   883  	//   selector:
   884  	//     foo: bar
   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  // ExampleVersionedAPIProcessor shows how to use the VersionedAPIProcessor and TemplateProcessor to
  1026  // build functions that implement complex multi-version APIs that require defaulting and validation.
  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  	// Output:
  1050  	// apiVersion: config.kubernetes.io/v1
  1051  	// kind: ResourceList
  1052  	// items:
  1053  	// - apiVersion: apps/v1
  1054  	//   kind: Deployment
  1055  	//   metadata:
  1056  	//     name: my-app
  1057  	//     selector:
  1058  	//       app: my-app
  1059  	//   spec:
  1060  	//     replicas: 3
  1061  	//     template:
  1062  	//       spec:
  1063  	//         containers:
  1064  	//         - name: app
  1065  	//           image: example.docker.com/team/app:1.0
  1066  	//           ports:
  1067  	//           - containerPort: 80
  1068  	//             name: http
  1069  	// - apiVersion: v1
  1070  	//   kind: Service
  1071  	//   metadata:
  1072  	//     name: my-app-svc
  1073  	//   spec:
  1074  	//     selector:
  1075  	//       app: my-app
  1076  	//     ports:
  1077  	//     - protocol: TCP
  1078  	//       port: 80
  1079  	//       targetPort: 80
  1080  	// - apiVersion: networking.k8s.io/v1
  1081  	//   kind: Ingress
  1082  	//   metadata:
  1083  	//     name: my-app-ingress
  1084  	//   spec:
  1085  	//     tls:
  1086  	//     - hosts:
  1087  	//       - demo.example.com
  1088  	//       secretName: secret-tls
  1089  	//     defaultBackend:
  1090  	//       service:
  1091  	//         name: my-app
  1092  	//         port:
  1093  	//           number: 80
  1094  	// functionConfig:
  1095  	//   apiVersion: example.com/v1alpha1
  1096  	//   kind: JavaSpringBoot
  1097  	//   metadata:
  1098  	//     name: my-app
  1099  	//   spec:
  1100  	//     image: example.docker.com/team/app:1.0
  1101  	//     domain: demo.example.com
  1102  }
  1103  

View as plain text