...

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

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

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package framework_test
     5  
     6  import (
     7  	"bytes"
     8  	"regexp"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
    15  	"k8s.io/kube-openapi/pkg/validation/spec"
    16  	"sigs.k8s.io/kustomize/kyaml/errors"
    17  	"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
    18  	"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
    19  	"sigs.k8s.io/kustomize/kyaml/openapi"
    20  	"sigs.k8s.io/kustomize/kyaml/resid"
    21  	"sigs.k8s.io/kustomize/kyaml/yaml"
    22  
    23  	"sigs.k8s.io/kustomize/kyaml/fn/framework"
    24  	"sigs.k8s.io/kustomize/kyaml/kio"
    25  )
    26  
    27  func TestTemplateProcessor_ResourceTemplates(t *testing.T) {
    28  	type API struct {
    29  		Image string `json:"image" yaml:"image"`
    30  	}
    31  
    32  	p := framework.TemplateProcessor{
    33  		TemplateData: &API{},
    34  		ResourceTemplates: []framework.ResourceTemplate{{
    35  			Templates: parser.TemplateFiles("testdata/template-processor/templates/basic"),
    36  		}},
    37  	}
    38  
    39  	out := new(bytes.Buffer)
    40  	rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
    41  apiVersion: config.kubernetes.io/v1
    42  kind: ResourceList
    43  items:
    44  - apiVersion: v1
    45    kind: Service
    46  functionConfig:
    47    image: baz
    48  `),
    49  		Writer: out}
    50  
    51  	require.NoError(t, framework.Execute(p, rw))
    52  	require.Equal(t, strings.TrimSpace(`
    53  apiVersion: config.kubernetes.io/v1
    54  kind: ResourceList
    55  items:
    56  - apiVersion: v1
    57    kind: Service
    58  - apiVersion: apps/v1
    59    kind: Deployment
    60    metadata:
    61      name: foo
    62      namespace: bar
    63    spec:
    64      template:
    65        spec:
    66          containers:
    67          - name: foo
    68            image: baz
    69  functionConfig:
    70    image: baz
    71  `), strings.TrimSpace(out.String()))
    72  }
    73  
    74  func TestTemplateProcessor_PatchTemplates(t *testing.T) {
    75  	type API struct {
    76  		Spec struct {
    77  			Replicas int    `json:"replicas" yaml:"replicas"`
    78  			A        string `json:"a" yaml:"a"`
    79  		} `json:"spec" yaml:"spec"`
    80  	}
    81  
    82  	config := &API{}
    83  	p := framework.TemplateProcessor{
    84  		TemplateData: config,
    85  		PatchTemplates: []framework.PatchTemplate{
    86  			// Patch from dir with no selector templating
    87  			&framework.ResourcePatchTemplate{
    88  				Templates: parser.TemplateFiles("testdata/template-processor/patches/basic"),
    89  				Selector:  &framework.Selector{Names: []string{"foo"}},
    90  			},
    91  			// Patch from string with selector templating
    92  			&framework.ResourcePatchTemplate{
    93  				Selector: &framework.Selector{Names: []string{"{{.Spec.A}}"}, TemplateData: &config},
    94  				Templates: parser.TemplateStrings(`
    95  metadata:
    96    annotations:
    97      baz: buz
    98  `)},
    99  		},
   100  	}
   101  	out := new(bytes.Buffer)
   102  
   103  	rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
   104  apiVersion: config.kubernetes.io/v1
   105  kind: ResourceList
   106  items:
   107  - apiVersion: apps/v1
   108    kind: Deployment
   109    metadata:
   110      name: foo
   111    spec:
   112      template:
   113        spec:
   114          containers:
   115          - name: foo
   116            image: baz
   117  - apiVersion: apps/v1
   118    kind: Deployment
   119    metadata:
   120      name: bar
   121    spec:
   122      template:
   123        spec:
   124          containers:
   125          - name: foo
   126            image: baz
   127  functionConfig:
   128    spec:
   129      replicas: 5
   130      a: bar
   131  `),
   132  		Writer: out}
   133  
   134  	require.NoError(t, framework.Execute(p, rw))
   135  	require.Equal(t, strings.TrimSpace(`
   136  apiVersion: config.kubernetes.io/v1
   137  kind: ResourceList
   138  items:
   139  - apiVersion: apps/v1
   140    kind: Deployment
   141    metadata:
   142      name: foo
   143    spec:
   144      template:
   145        spec:
   146          containers:
   147          - name: foo
   148            image: baz
   149      replicas: 5
   150  - apiVersion: apps/v1
   151    kind: Deployment
   152    metadata:
   153      name: bar
   154      annotations:
   155        baz: buz
   156    spec:
   157      template:
   158        spec:
   159          containers:
   160          - name: foo
   161            image: baz
   162  functionConfig:
   163    spec:
   164      replicas: 5
   165      a: bar
   166  `), strings.TrimSpace(out.String()))
   167  }
   168  
   169  func TestTemplateProcessor_ContainerPatchTemplates(t *testing.T) {
   170  	type API struct {
   171  		Spec struct {
   172  			Key   string `json:"key" yaml:"key"`
   173  			Value string `json:"value" yaml:"value"`
   174  			A     string `json:"a" yaml:"a"`
   175  		}
   176  	}
   177  
   178  	config := &API{}
   179  	p := framework.TemplateProcessor{
   180  		TemplateData: config,
   181  		PatchTemplates: []framework.PatchTemplate{
   182  			// patch from dir with no selector templating
   183  			&framework.ContainerPatchTemplate{
   184  				Templates: parser.TemplateFiles("testdata/template-processor/container-patches"),
   185  				Selector:  &framework.Selector{Names: []string{"foo"}},
   186  			},
   187  			// patch from string with selector templating
   188  			&framework.ContainerPatchTemplate{
   189  				Selector: &framework.Selector{Names: []string{"{{.Spec.A}}"}, TemplateData: &config},
   190  				Templates: parser.TemplateStrings(`
   191  env:
   192  - name: Foo
   193    value: Bar
   194  `)},
   195  		},
   196  	}
   197  
   198  	out := new(bytes.Buffer)
   199  	rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
   200  apiVersion: config.kubernetes.io/v1
   201  kind: ResourceList
   202  items:
   203  - apiVersion: apps/v1
   204    kind: Deployment
   205    metadata:
   206      name: foo
   207    spec:
   208      template:
   209        spec:
   210          containers:
   211          - name: a
   212            env:
   213            - name: EXISTING
   214              value: variable
   215          - name: b
   216          - name: c
   217  - apiVersion: apps/v1
   218    kind: Deployment
   219    metadata:
   220      name: bar
   221    spec:
   222      template:
   223        spec:
   224          containers:
   225          - name: foo
   226            image: baz
   227  functionConfig:
   228    spec:
   229      key: Hello
   230      value: World
   231      a: bar
   232  `),
   233  		Writer: out}
   234  
   235  	require.NoError(t, framework.Execute(p, rw))
   236  	require.Equal(t, strings.TrimSpace(`
   237  apiVersion: config.kubernetes.io/v1
   238  kind: ResourceList
   239  items:
   240  - apiVersion: apps/v1
   241    kind: Deployment
   242    metadata:
   243      name: foo
   244    spec:
   245      template:
   246        spec:
   247          containers:
   248          - name: a
   249            env:
   250            - name: EXISTING
   251              value: variable
   252            - name: Hello
   253              value: World
   254          - name: b
   255            env:
   256            - name: Hello
   257              value: World
   258          - name: c
   259            env:
   260            - name: Hello
   261              value: World
   262  - apiVersion: apps/v1
   263    kind: Deployment
   264    metadata:
   265      name: bar
   266    spec:
   267      template:
   268        spec:
   269          containers:
   270          - name: foo
   271            image: baz
   272            env:
   273            - name: Foo
   274              value: Bar
   275  functionConfig:
   276    spec:
   277      key: Hello
   278      value: World
   279      a: bar
   280  `), strings.TrimSpace(out.String()))
   281  }
   282  
   283  func TestTemplateProcessor_ContainerPatchTemplates_MultipleWorkloadKinds(t *testing.T) {
   284  	type API struct {
   285  		Spec struct {
   286  			Key   string `json:"key" yaml:"key"`
   287  			Value string `json:"value" yaml:"value"`
   288  			A     string `json:"a" yaml:"a"`
   289  		}
   290  	}
   291  	config := &API{}
   292  	p := framework.TemplateProcessor{
   293  		TemplateData: config,
   294  		ResourceTemplates: []framework.ResourceTemplate{{
   295  			Templates: parser.TemplateFiles("testdata/template-processor/templates/container-sources"),
   296  		}},
   297  		PatchTemplates: []framework.PatchTemplate{
   298  			&framework.ContainerPatchTemplate{
   299  				Templates: parser.TemplateFiles("testdata/template-processor/container-patches"),
   300  			},
   301  		},
   302  	}
   303  
   304  	out := new(bytes.Buffer)
   305  	rw := &kio.ByteReadWriter{Writer: out, Reader: bytes.NewBufferString(`
   306  apiVersion: config.kubernetes.io/v1
   307  kind: ResourceList
   308  items: []
   309  functionConfig:
   310    spec:
   311      key: Hello
   312      value: World
   313      a: bar
   314  `)}
   315  
   316  	require.NoError(t, framework.Execute(p, rw))
   317  	resources, err := (&kio.ByteReader{Reader: out}).Read()
   318  	require.NoError(t, err)
   319  	envRegex := regexp.MustCompile(strings.TrimSpace(`
   320  \s+ env:
   321  \s+ - name: EXISTING
   322  \s+   value: variable
   323  \s+ - name: Hello
   324  \s+   value: World
   325  `))
   326  	require.Equal(t, 9, len(resources))
   327  	for i, r := range resources {
   328  		t.Run(r.GetKind(), func(t *testing.T) {
   329  			assert.Regexp(t, envRegex, resources[i].MustString())
   330  		})
   331  	}
   332  }
   333  
   334  func TestSimpleProcessor_Process_loads_config(t *testing.T) {
   335  	cfg := new(struct {
   336  		Value string `yaml:"value"`
   337  	})
   338  	p := framework.SimpleProcessor{
   339  		Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
   340  			if cfg.Value != "dataFromResourceList" {
   341  				return nil, errors.Errorf("got incorrect config value %q", cfg.Value)
   342  			}
   343  			return items, nil
   344  		}),
   345  		Config: &cfg,
   346  	}
   347  	rl := framework.ResourceList{
   348  		FunctionConfig: yaml.NewMapRNode(&map[string]string{
   349  			"value": "dataFromResourceList",
   350  		}),
   351  	}
   352  	require.NoError(t, p.Process(&rl))
   353  }
   354  
   355  func TestSimpleProcessor_Process_Error(t *testing.T) {
   356  	tests := []struct {
   357  		name    string
   358  		filter  kio.Filter
   359  		config  interface{}
   360  		wantErr string
   361  	}{
   362  		{
   363  			name:    "error when filter is nil",
   364  			config:  map[string]string{},
   365  			filter:  nil,
   366  			wantErr: "processing filter: ResourceList cannot run apply nil filter",
   367  		}, {
   368  			name:   "no error when config is nil",
   369  			config: nil,
   370  			filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
   371  				return items, nil
   372  			}),
   373  			wantErr: "",
   374  		},
   375  		{
   376  			name:    "error in filter",
   377  			wantErr: "processing filter: err from filter",
   378  			filter: kio.FilterFunc(func(_ []*yaml.RNode) ([]*yaml.RNode, error) {
   379  				return nil, errors.Errorf("err from filter")
   380  			}),
   381  		},
   382  	}
   383  	for _, tt := range tests {
   384  		tt := tt
   385  		t.Run(tt.name, func(t *testing.T) {
   386  			p := framework.SimpleProcessor{
   387  				Filter: tt.filter,
   388  				Config: tt.config,
   389  			}
   390  			rl := framework.ResourceList{
   391  				FunctionConfig: yaml.NewMapRNode(&map[string]string{
   392  					"value": "dataFromResourceList",
   393  				}),
   394  			}
   395  			err := p.Process(&rl)
   396  			if tt.wantErr == "" {
   397  				require.NoError(t, err)
   398  			} else {
   399  				assert.EqualError(t, err, tt.wantErr)
   400  			}
   401  		})
   402  	}
   403  }
   404  
   405  func TestVersionedAPIProcessor_Process_Error(t *testing.T) {
   406  	tests := []struct {
   407  		name           string
   408  		filterProvider framework.FilterProvider
   409  		apiVersion     string
   410  		kind           string
   411  		wantErr        string
   412  	}{
   413  		{
   414  			name: "error in filter",
   415  			filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
   416  				return &framework.AndSelector{FailOnEmptyMatch: true}, nil
   417  			}),
   418  			wantErr: "selector did not select any items",
   419  		},
   420  		{
   421  			name: "error GVKFilterMap no filter for kind",
   422  			filterProvider: framework.GVKFilterMap{
   423  				"puppy": {
   424  					"pets.example.com/v1beta1": &framework.Selector{},
   425  				},
   426  			},
   427  			kind:       "kitten",
   428  			apiVersion: "pets.example.com/v1beta1",
   429  			wantErr:    "kind \"kitten\" is not supported",
   430  		},
   431  		{
   432  			name: "error GVKFilterMap no filter for version",
   433  			filterProvider: framework.GVKFilterMap{
   434  				"kitten": {
   435  					"pets.example.com/v1alpha1": &framework.Selector{},
   436  				},
   437  			},
   438  			kind:       "kitten",
   439  			apiVersion: "pets.example.com/v1beta1",
   440  			wantErr:    "apiVersion \"pets.example.com/v1beta1\" is not supported for kind \"kitten\"",
   441  		},
   442  		{
   443  			name:           "error GVKFilterMap blank kind",
   444  			filterProvider: framework.GVKFilterMap{},
   445  			kind:           "",
   446  			apiVersion:     "pets.example.com/v1beta1",
   447  			wantErr:        "unable to identify provider for resource: kind is required",
   448  		},
   449  		{
   450  			name:           "error GVKFilterMap blank version",
   451  			filterProvider: framework.GVKFilterMap{},
   452  			kind:           "kitten",
   453  			apiVersion:     "",
   454  			wantErr:        "unable to identify provider for resource: apiVersion is required",
   455  		},
   456  	}
   457  	for _, tt := range tests {
   458  		tt := tt
   459  		t.Run(tt.name, func(t *testing.T) {
   460  			p := framework.VersionedAPIProcessor{
   461  				FilterProvider: tt.filterProvider,
   462  			}
   463  			rl := framework.ResourceList{
   464  				FunctionConfig: yaml.NewMapRNode(&map[string]string{
   465  					"apiVersion": tt.apiVersion,
   466  					"kind":       tt.kind,
   467  				}),
   468  			}
   469  			err := p.Process(&rl)
   470  			require.Error(t, err)
   471  			assert.Contains(t, err.Error(), tt.wantErr)
   472  		})
   473  	}
   474  }
   475  
   476  func TestTemplateProcessor_Process_Error(t *testing.T) {
   477  	tests := []struct {
   478  		name      string
   479  		processor framework.TemplateProcessor
   480  		wantErr   string
   481  	}{
   482  		{
   483  			name: "ResourcePatchTemplate is not a resource",
   484  			processor: framework.TemplateProcessor{
   485  				PatchTemplates: []framework.PatchTemplate{
   486  					&framework.ResourcePatchTemplate{
   487  						Templates: parser.TemplateStrings(`aString
   488  another`),
   489  					}},
   490  			},
   491  			wantErr: `failed to parse rendered patch template into a resource:
   492  001 aString
   493  002 another
   494  : wrong node kind: expected MappingNode but got ScalarNode: node contents:
   495  aString another
   496  `,
   497  		},
   498  		{
   499  			name: "ResourcePatchTemplate is invalid template",
   500  			processor: framework.TemplateProcessor{
   501  				PatchTemplates: []framework.PatchTemplate{
   502  					&framework.ResourcePatchTemplate{
   503  						Templates: parser.TemplateStrings("foo: {{ .OOPS }}"),
   504  					}},
   505  			},
   506  			wantErr: "can't evaluate field OOPS",
   507  		},
   508  		{
   509  			name: "ContainerPatchTemplate is not a resource",
   510  			processor: framework.TemplateProcessor{
   511  				PatchTemplates: []framework.PatchTemplate{
   512  					&framework.ContainerPatchTemplate{
   513  						Templates: parser.TemplateStrings(`aString
   514  another`),
   515  					}},
   516  			},
   517  			wantErr: `failed to parse rendered patch template into a resource:
   518  001 aString
   519  002 another
   520  : wrong node kind: expected MappingNode but got ScalarNode: node contents:
   521  aString another
   522  `,
   523  		},
   524  		{
   525  			name: "ContainerPatchTemplate is invalid template",
   526  			processor: framework.TemplateProcessor{
   527  				PatchTemplates: []framework.PatchTemplate{
   528  					&framework.ContainerPatchTemplate{
   529  						Templates: parser.TemplateStrings("foo: {{ .OOPS }}"),
   530  					}},
   531  			},
   532  			wantErr: "can't evaluate field OOPS",
   533  		},
   534  		{
   535  			name: "ResourceTemplate is not a resource",
   536  			processor: framework.TemplateProcessor{
   537  				ResourceTemplates: []framework.ResourceTemplate{{
   538  					Templates: parser.TemplateStrings(`aString
   539  another`),
   540  				}},
   541  			},
   542  			wantErr: `failed to parse rendered template into a resource:
   543  001 aString
   544  002 another
   545  : wrong node kind: expected MappingNode but got ScalarNode: node contents:
   546  aString another
   547  `,
   548  		},
   549  		{
   550  			name: "ResourceTemplate is invalid template",
   551  			processor: framework.TemplateProcessor{
   552  				ResourceTemplates: []framework.ResourceTemplate{{
   553  					Templates: parser.TemplateStrings("foo: {{ .OOPS }}"),
   554  				}},
   555  			},
   556  			wantErr: "can't evaluate field OOPS",
   557  		},
   558  	}
   559  	for _, tt := range tests {
   560  		tt := tt
   561  		t.Run(tt.name, func(t *testing.T) {
   562  			rl := framework.ResourceList{
   563  				Items: []*yaml.RNode{
   564  					yaml.MustParse(`
   565  kind: Deployment
   566  apiVersion: apps/v1
   567  metadata:
   568    name: foo
   569  spec:
   570    replicas: 5
   571    template:
   572      spec:
   573        containers:
   574        - name: foo
   575  `),
   576  				},
   577  				FunctionConfig: yaml.NewMapRNode(&map[string]string{
   578  					"value": "dataFromResourceList",
   579  				}),
   580  			}
   581  			tt.processor.TemplateData = new(struct {
   582  				Value string `yaml:"value"`
   583  			})
   584  			err := tt.processor.Process(&rl)
   585  			require.Error(t, err)
   586  			assert.Contains(t, err.Error(), tt.wantErr)
   587  		})
   588  	}
   589  }
   590  
   591  func TestTemplateProcessor_AdditionalSchemas(t *testing.T) {
   592  	p := framework.TemplateProcessor{
   593  		AdditionalSchemas: parser.SchemaFiles("testdata/template-processor/schemas"),
   594  		ResourceTemplates: []framework.ResourceTemplate{{
   595  			Templates: parser.TemplateFiles("testdata/template-processor/templates/custom-resource/foo.template.yaml"),
   596  		}},
   597  		PatchTemplates: []framework.PatchTemplate{
   598  			&framework.ResourcePatchTemplate{
   599  				Templates: parser.TemplateFiles("testdata/template-processor/patches/custom-resource/patch.template.yaml")},
   600  		},
   601  	}
   602  	out := new(bytes.Buffer)
   603  
   604  	rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
   605  apiVersion: config.kubernetes.io/v1
   606  kind: ResourceList
   607  items:
   608  - apiVersion: example.com/v1
   609    kind: Foo
   610    metadata:
   611      name: source
   612    spec:
   613      targets:
   614      - app: C
   615        size: medium
   616  `),
   617  		Writer: out}
   618  	require.NoError(t, framework.Execute(p, rw))
   619  	require.Equal(t, strings.TrimSpace(`
   620  apiVersion: config.kubernetes.io/v1
   621  kind: ResourceList
   622  items:
   623  - apiVersion: example.com/v1
   624    kind: Foo
   625    metadata:
   626      name: source
   627    spec:
   628      targets:
   629      - app: C
   630        size: large
   631        type: Ruby
   632      - app: B
   633        size: small
   634  - apiVersion: example.com/v1
   635    kind: Foo
   636    metadata:
   637      name: example
   638    spec:
   639      targets:
   640      - app: A
   641        type: Go
   642        size: small
   643      - app: B
   644        type: Go
   645        size: small
   646      - app: C
   647        type: Ruby
   648        size: large
   649  `), strings.TrimSpace(out.String()))
   650  	found := openapi.SchemaForResourceType(yaml.TypeMeta{
   651  		APIVersion: "example.com/v1",
   652  		Kind:       "Foo",
   653  	})
   654  	require.Nil(t, found, "openAPI schema was not reset")
   655  }
   656  
   657  func TestTemplateProcessor_Validator(t *testing.T) {
   658  	// This test proves the Validate method is called when implemented
   659  	// and demonstrates the use of ProcessorResultsChecker's error matching
   660  	p := func() framework.ResourceListProcessor {
   661  		return &framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{
   662  			"JavaSpringBoot": {
   663  				"example.com/v1alpha1": &v1alpha1JavaSpringBoot{},
   664  			}}}
   665  	}
   666  	c := frameworktestutil.ProcessorResultsChecker{
   667  		TestDataDirectory: "testdata/validation",
   668  		Processor:         p,
   669  	}
   670  	c.Assert(t)
   671  }
   672  
   673  type jsonTagTest struct {
   674  	Name string `json:"name"`
   675  	Test bool   `json:"test"`
   676  }
   677  
   678  type yamlTagTest struct {
   679  	Name string `yaml:"name"`
   680  	Test bool   `yaml:"test"`
   681  }
   682  
   683  type customErrorTest struct {
   684  	v1alpha1JavaSpringBoot
   685  }
   686  
   687  func (e customErrorTest) Schema() (*spec.Schema, error) {
   688  	return e.v1alpha1JavaSpringBoot.Schema()
   689  }
   690  
   691  func (e customErrorTest) Validate() error {
   692  	return errors.Errorf("Custom errors:\n- first error\n- second error")
   693  }
   694  
   695  type errorMergeTest struct {
   696  	v1alpha1JavaSpringBoot
   697  }
   698  
   699  func (e errorMergeTest) Schema() (*spec.Schema, error) {
   700  	return e.v1alpha1JavaSpringBoot.Schema()
   701  }
   702  
   703  func (e errorMergeTest) Validate() error {
   704  	if strings.HasSuffix(e.Spec.Image, "latest") {
   705  		return validationErrors.CompositeValidationError(errors.Errorf("spec.image cannot be tagged :latest"))
   706  	}
   707  	return nil
   708  }
   709  
   710  func (a schemaProviderOnlyTest) Schema() (*spec.Schema, error) {
   711  	schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk("example.com", "v1alpha1", "JavaSpringBoot"), javaSpringBootDefinition)
   712  	return schema, errors.WrapPrefixf(err, "parsing JavaSpringBoot schema")
   713  }
   714  
   715  type schemaProviderOnlyTest struct {
   716  	Metadata Metadata                   `yaml:"metadata" json:"metadata"`
   717  	Spec     v1alpha1JavaSpringBootSpec `yaml:"spec" json:"spec"`
   718  }
   719  
   720  func TestLoadFunctionConfig(t *testing.T) {
   721  	tests := []struct {
   722  		name        string
   723  		src         *yaml.RNode
   724  		api         interface{}
   725  		want        interface{}
   726  		wantErrMsgs []string
   727  	}{
   728  		{
   729  			name: "combines schema-based and non-composite custom errors",
   730  			src: yaml.MustParse(`
   731  apiVersion: example.com/v1alpha1 
   732  kind: JavaSpringBoot
   733  spec:
   734    replicas: 11
   735    domain: foo.myco.io
   736    image: nginx:latest
   737  `),
   738  			api: &customErrorTest{},
   739  			wantErrMsgs: []string{
   740  				"validation failure list:",
   741  				"spec.replicas in body should be less than or equal to 9",
   742  				"spec.domain in body should match 'example\\.com$'",
   743  				`Custom errors:
   744  - first error
   745  - second error`,
   746  			},
   747  		},
   748  		{
   749  			name: "merges schema-based errors with custom composite errors",
   750  			src: yaml.MustParse(`
   751  apiVersion: example.com/v1alpha1 
   752  kind: JavaSpringBoot
   753  spec:
   754    replicas: 11
   755    domain: foo.myco.io
   756    image: nginx:latest
   757  `),
   758  			api: &errorMergeTest{},
   759  			wantErrMsgs: []string{"validation failure list:",
   760  				"spec.replicas in body should be less than or equal to 9",
   761  				"spec.domain in body should match 'example\\.com$'",
   762  				"spec.image cannot be tagged :latest"},
   763  		},
   764  		{
   765  			name: "schema errors only",
   766  			src: yaml.MustParse(`
   767  apiVersion: example.com/v1alpha1 
   768  kind: JavaSpringBoot
   769  spec:
   770    replicas: 11
   771  `),
   772  			api: &errorMergeTest{},
   773  			wantErrMsgs: []string{
   774  				`validation failure list:
   775  spec.replicas in body should be less than or equal to 9`,
   776  			},
   777  		}, {
   778  			name: "schema provider only",
   779  			src: yaml.MustParse(`
   780  apiVersion: example.com/v1alpha1
   781  kind: JavaSpringBoot
   782  spec:
   783    replicas: 11
   784  `),
   785  			api: &schemaProviderOnlyTest{},
   786  			wantErrMsgs: []string{
   787  				`validation failure list:
   788  spec.replicas in body should be less than or equal to 9`,
   789  			},
   790  		},
   791  		{
   792  			name: "custom errors only",
   793  			src: yaml.MustParse(`
   794  apiVersion: example.com/v1alpha1 
   795  kind: JavaSpringBoot
   796  spec:
   797    image: nginx:latest
   798  `),
   799  			api: &errorMergeTest{},
   800  			wantErrMsgs: []string{
   801  				`validation failure list:
   802  spec.image cannot be tagged :latest`},
   803  		},
   804  		{
   805  			name: "both custom and schema error hooks defined, but no errors produced",
   806  			src: yaml.MustParse(`
   807  apiVersion: example.com/v1alpha1 
   808  kind: JavaSpringBoot
   809  spec:
   810    image: nginx:1.0
   811    replicas: 3
   812    domain: bar.example.com
   813  `),
   814  			api: &errorMergeTest{},
   815  			want: &errorMergeTest{v1alpha1JavaSpringBoot: v1alpha1JavaSpringBoot{
   816  				Spec: v1alpha1JavaSpringBootSpec{Replicas: 3, Domain: "bar.example.com", Image: "nginx:1.0"}},
   817  			},
   818  		},
   819  		{
   820  			name: "successfully loads types that include fields only tagged with json markers",
   821  			src: yaml.MustParse(`
   822  name: tester
   823  test: true
   824  `),
   825  			api:  &jsonTagTest{},
   826  			want: &jsonTagTest{Name: "tester", Test: true},
   827  		},
   828  		{
   829  			name: "successfully loads types that include fields only tagged with yaml markers",
   830  			src: yaml.MustParse(`
   831  name: tester
   832  test: true
   833  `),
   834  			api:  &yamlTagTest{},
   835  			want: &yamlTagTest{Name: "tester", Test: true},
   836  		},
   837  	}
   838  
   839  	for _, tt := range tests {
   840  		t.Run(tt.name, func(t *testing.T) {
   841  			err := framework.LoadFunctionConfig(tt.src, tt.api)
   842  			if len(tt.wantErrMsgs) == 0 {
   843  				require.NoError(t, err)
   844  				require.Equal(t, tt.want, tt.api)
   845  			} else {
   846  				require.Error(t, err)
   847  				for _, msg := range tt.wantErrMsgs {
   848  					require.Contains(t, err.Error(), msg)
   849  				}
   850  			}
   851  		})
   852  	}
   853  }
   854  

View as plain text