...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil/runtimeutil_test.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package runtimeutil
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"sigs.k8s.io/kustomize/kyaml/yaml"
    15  )
    16  
    17  type testRun struct {
    18  	err           error
    19  	expectedInput string
    20  	output        string
    21  	t             *testing.T
    22  }
    23  
    24  func (r testRun) run(reader io.Reader, writer io.Writer) error {
    25  	if r.expectedInput != "" {
    26  		input, err := io.ReadAll(reader)
    27  		if !assert.NoError(r.t, err) {
    28  			r.t.FailNow()
    29  		}
    30  
    31  		// verify input matches expected
    32  		if !assert.Equal(r.t, r.expectedInput, string(input)) {
    33  			r.t.FailNow()
    34  		}
    35  	}
    36  
    37  	_, err := writer.Write([]byte(r.output))
    38  	if !assert.NoError(r.t, err) {
    39  		r.t.FailNow()
    40  	}
    41  
    42  	return r.err
    43  }
    44  
    45  func TestFunctionFilter_Filter(t *testing.T) {
    46  	var tests = []struct {
    47  		run                testRun
    48  		name               string
    49  		input              []string
    50  		functionConfig     string
    51  		expectedOutput     []string
    52  		expectedError      string
    53  		expectedSavedError string
    54  		expectedResults    string
    55  		noMakeResultsFile  bool
    56  		instance           FunctionFilter
    57  	}{
    58  		// verify that resources emitted from the function have a file path defaulted
    59  		// if none already exists
    60  		{
    61  			name: "default file path annotation",
    62  			run: testRun{
    63  				output: `
    64  apiVersion: config.kubernetes.io/v1
    65  kind: ResourceList
    66  items:
    67  - apiVersion: apps/v1
    68    kind: Deployment
    69    metadata:
    70      name: deployment-foo
    71  - apiVersion: v1
    72    kind: Service
    73    metadata:
    74      name: service-foo
    75  `,
    76  			},
    77  			expectedOutput: []string{
    78  				`
    79  apiVersion: apps/v1
    80  kind: Deployment
    81  metadata:
    82    name: deployment-foo
    83    annotations:
    84      internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
    85      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
    86  `,
    87  				`
    88  apiVersion: v1
    89  kind: Service
    90  metadata:
    91    name: service-foo
    92    annotations:
    93      internal.config.kubernetes.io/path: 'service_service-foo.yaml'
    94      config.kubernetes.io/path: 'service_service-foo.yaml'
    95  `,
    96  			},
    97  		},
    98  
    99  		// verify that resources emitted from the function do not have a file path defaulted
   100  		// if one already exists
   101  		{
   102  			name: "no default file path annotation",
   103  			run: testRun{
   104  				output: `
   105  apiVersion: config.kubernetes.io/v1
   106  kind: ResourceList
   107  items:
   108  - apiVersion: apps/v1
   109    kind: Deployment
   110    metadata:
   111      name: deployment-foo
   112  - apiVersion: v1
   113    kind: Service
   114    metadata:
   115      name: service-foo
   116      annotations:
   117       config.kubernetes.io/path: 'foo.yaml'
   118  `,
   119  			},
   120  			expectedOutput: []string{
   121  				`
   122  apiVersion: apps/v1
   123  kind: Deployment
   124  metadata:
   125    name: deployment-foo
   126    annotations:
   127      internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   128      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   129  `,
   130  				`
   131  apiVersion: v1
   132  kind: Service
   133  metadata:
   134    name: service-foo
   135    annotations:
   136      config.kubernetes.io/path: 'foo.yaml'
   137      internal.config.kubernetes.io/path: 'foo.yaml'
   138  `,
   139  			},
   140  		},
   141  
   142  		// verify the FunctionFilter correctly writes the inputs and reads the outputs
   143  		// of Run
   144  		{
   145  			name: "write read",
   146  			run: testRun{
   147  				output: `
   148  apiVersion: config.kubernetes.io/v1
   149  kind: ResourceList
   150  items:
   151  - apiVersion: v1
   152    kind: Service
   153    metadata:
   154      name: service-foo
   155      annotations:
   156        config.kubernetes.io/path: 'foo.yaml'
   157  - apiVersion: v1
   158    kind: ConfigMap
   159    metadata:
   160      name: configmap-foo
   161      annotations:
   162        config.kubernetes.io/path: 'foo.yaml'
   163  `,
   164  			},
   165  			input: []string{
   166  				`
   167  apiVersion: apps/v1
   168  kind: Deployment
   169  metadata:
   170    name: deployment-foo
   171  `,
   172  				`
   173  apiVersion: v1
   174  kind: Service
   175  metadata:
   176    name: service-foo
   177  `,
   178  			},
   179  			expectedOutput: []string{`
   180  apiVersion: v1
   181  kind: Service
   182  metadata:
   183    name: service-foo
   184    annotations:
   185      config.kubernetes.io/path: 'foo.yaml'
   186      internal.config.kubernetes.io/path: 'foo.yaml'
   187  `,
   188  				`
   189  apiVersion: v1
   190  kind: ConfigMap
   191  metadata:
   192    name: configmap-foo
   193    annotations:
   194      config.kubernetes.io/path: 'foo.yaml'
   195      internal.config.kubernetes.io/path: 'foo.yaml'
   196  `,
   197  			},
   198  		},
   199  
   200  		// verify that the results file is written
   201  		//
   202  		{
   203  			name: "write results file",
   204  			run: testRun{
   205  				output: `apiVersion: config.kubernetes.io/v1
   206  kind: ResourceList
   207  items:
   208  - apiVersion: apps/v1
   209    kind: Deployment
   210    metadata:
   211      name: deployment-foo
   212  - apiVersion: v1
   213    kind: Service
   214    metadata:
   215      name: service-foo
   216  results:
   217  - apiVersion: config.k8s.io/v1alpha1
   218    kind: ObjectError
   219    name: "some-validator"
   220    items:
   221    - type: error
   222      message: "some message"
   223      resourceRef:
   224        apiVersion: apps/v1
   225        kind: Deployment
   226        name: foo
   227        namespace: bar
   228      file:
   229        path: deploy.yaml
   230        index: 0
   231      field:
   232        path: "spec.template.spec.containers[3].resources.limits.cpu"
   233        currentValue: "200"
   234        suggestedValue: "2"
   235  `,
   236  			},
   237  			expectedOutput: []string{`
   238  apiVersion: apps/v1
   239  kind: Deployment
   240  metadata:
   241    name: deployment-foo
   242    annotations:
   243      internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   244      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   245  `, `
   246  apiVersion: v1
   247  kind: Service
   248  metadata:
   249    name: service-foo
   250    annotations:
   251      internal.config.kubernetes.io/path: 'service_service-foo.yaml'
   252      config.kubernetes.io/path: 'service_service-foo.yaml'
   253  `,
   254  			},
   255  			expectedResults: `
   256  - apiVersion: config.k8s.io/v1alpha1
   257    kind: ObjectError
   258    name: "some-validator"
   259    items:
   260    - type: error
   261      message: "some message"
   262      resourceRef:
   263        apiVersion: apps/v1
   264        kind: Deployment
   265        name: foo
   266        namespace: bar
   267      file:
   268        path: deploy.yaml
   269        index: 0
   270      field:
   271        path: "spec.template.spec.containers[3].resources.limits.cpu"
   272        currentValue: "200"
   273        suggestedValue: "2"
   274  `,
   275  		},
   276  
   277  		// verify that the results file is written for functions that exist non-0
   278  		// and the FunctionFilter returns the error
   279  		{
   280  			name:          "write results file function exit non 0",
   281  			expectedError: "failed",
   282  			run: testRun{
   283  				err: fmt.Errorf("failed"),
   284  				output: `
   285  apiVersion: config.kubernetes.io/v1
   286  kind: ResourceList
   287  items:
   288  - apiVersion: apps/v1
   289    kind: Deployment
   290    metadata:
   291      name: deployment-foo
   292  - apiVersion: v1
   293    kind: Service
   294    metadata:
   295      name: service-foo
   296  results:
   297  - apiVersion: config.k8s.io/v1alpha1
   298    kind: ObjectError
   299    name: "some-validator"
   300    items:
   301    - type: error
   302      message: "some message"
   303      resourceRef:
   304        apiVersion: apps/v1
   305        kind: Deployment
   306        name: foo
   307        namespace: bar
   308      file:
   309        path: deploy.yaml
   310        index: 0
   311      field:
   312        path: "spec.template.spec.containers[3].resources.limits.cpu"
   313        currentValue: "200"
   314        suggestedValue: "2"
   315  `,
   316  			},
   317  			expectedOutput: []string{
   318  				`
   319  apiVersion: apps/v1
   320  kind: Deployment
   321  metadata:
   322    name: deployment-foo
   323    annotations:
   324      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   325      `, `
   326  apiVersion: v1
   327  kind: Service
   328  metadata:
   329    name: service-foo
   330    annotations:
   331      config.kubernetes.io/path: 'service_service-foo.yaml'
   332      `,
   333  			},
   334  			expectedResults: `
   335  - apiVersion: config.k8s.io/v1alpha1
   336    kind: ObjectError
   337    name: "some-validator"
   338    items:
   339    - type: error
   340      message: "some message"
   341      resourceRef:
   342        apiVersion: apps/v1
   343        kind: Deployment
   344        name: foo
   345        namespace: bar
   346      file:
   347        path: deploy.yaml
   348        index: 0
   349      field:
   350        path: "spec.template.spec.containers[3].resources.limits.cpu"
   351        currentValue: "200"
   352        suggestedValue: "2"
   353      `,
   354  		},
   355  
   356  		// verify that if deferFailure is set, the results file is written and the
   357  		// exit error is saved, but the FunctionFilter does not return an error.
   358  		{
   359  			name:               "write results defer failure",
   360  			instance:           FunctionFilter{DeferFailure: true},
   361  			expectedSavedError: "failed",
   362  			run: testRun{
   363  				err: fmt.Errorf("failed"),
   364  				output: `
   365  apiVersion: config.kubernetes.io/v1
   366  kind: ResourceList
   367  items:
   368  - apiVersion: apps/v1
   369    kind: Deployment
   370    metadata:
   371      name: deployment-foo
   372  - apiVersion: v1
   373    kind: Service
   374    metadata:
   375      name: service-foo
   376  results:
   377  - apiVersion: config.k8s.io/v1alpha1
   378    kind: ObjectError
   379    name: "some-validator"
   380    items:
   381    - type: error
   382      message: "some message"
   383      resourceRef:
   384        apiVersion: apps/v1
   385        kind: Deployment
   386        name: foo
   387        namespace: bar
   388      file:
   389        path: deploy.yaml
   390        index: 0
   391      field:
   392        path: "spec.template.spec.containers[3].resources.limits.cpu"
   393        currentValue: "200"
   394        suggestedValue: "2"`,
   395  			},
   396  			expectedOutput: []string{
   397  				`
   398  apiVersion: apps/v1
   399  kind: Deployment
   400  metadata:
   401    name: deployment-foo
   402    annotations:
   403      internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   404      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   405  		`, `
   406  apiVersion: v1
   407  kind: Service
   408  metadata:
   409    name: service-foo
   410    annotations:
   411      internal.config.kubernetes.io/path: 'service_service-foo.yaml'
   412      config.kubernetes.io/path: 'service_service-foo.yaml'
   413  		`,
   414  			},
   415  			expectedResults: `
   416  - apiVersion: config.k8s.io/v1alpha1
   417    kind: ObjectError
   418    name: "some-validator"
   419    items:
   420    - type: error
   421      message: "some message"
   422      resourceRef:
   423        apiVersion: apps/v1
   424        kind: Deployment
   425        name: foo
   426        namespace: bar
   427      file:
   428        path: deploy.yaml
   429        index: 0
   430      field:
   431        path: "spec.template.spec.containers[3].resources.limits.cpu"
   432        currentValue: "200"
   433        suggestedValue: "2"`,
   434  		},
   435  
   436  		{
   437  			name:              "write results bad results file",
   438  			expectedError:     "open /not/real/file:",
   439  			noMakeResultsFile: true,
   440  			run: testRun{
   441  				output: `
   442  apiVersion: config.kubernetes.io/v1
   443  kind: ResourceList
   444  items:
   445  - apiVersion: apps/v1
   446    kind: Deployment
   447    metadata:
   448      name: deployment-foo
   449  - apiVersion: v1
   450    kind: Service
   451    metadata:
   452      name: service-foo
   453  results:
   454  - apiVersion: config.k8s.io/v1alpha1
   455    name: "some-validator"
   456  `,
   457  			},
   458  			expectedOutput: []string{
   459  				`
   460  apiVersion: apps/v1
   461  kind: Deployment
   462  metadata:
   463    name: deployment-foo
   464    annotations:
   465      config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
   466  		`, `
   467  apiVersion: v1
   468  kind: Service
   469  metadata:
   470    name: service-foo
   471    annotations:
   472      config.kubernetes.io/path: 'service_service-foo.yaml'
   473  		`,
   474  			},
   475  			// these aren't written, expect an error
   476  			expectedResults: `
   477  - apiVersion: config.k8s.io/v1alpha1
   478    name: "some-validator"
   479  `,
   480  		},
   481  
   482  		// verify the function only sees resources scoped to it based on the directory
   483  		// containing the functionConfig and the directory containing each resource.
   484  		// resources not provided to the function should still appear in the FunctionFilter
   485  		// output
   486  		{
   487  			name: "scope resources by directory",
   488  			run: testRun{
   489  				expectedInput: `apiVersion: config.kubernetes.io/v1
   490  kind: ResourceList
   491  items:
   492  - apiVersion: v1
   493    kind: Service
   494    metadata:
   495      name: service-foo
   496      annotations:
   497        config.kubernetes.io/path: 'foo/bar/s.yaml'
   498        internal.config.kubernetes.io/id: '1'
   499        config.k8s.io/id: '1'
   500  functionConfig:
   501    apiVersion: example.com/v1
   502    kind: Example
   503    metadata:
   504      name: foo
   505      annotations:
   506        config.kubernetes.io/path: 'foo/bar.yaml'
   507  `,
   508  				output: `apiVersion: config.kubernetes.io/v1
   509  kind: ResourceList
   510  items:
   511  - apiVersion: v1
   512    kind: Service
   513    metadata:
   514      name: service-foo
   515      annotations:
   516        config.kubernetes.io/path: 'foo/bar/s.yaml'
   517        new: annotation
   518        internal.config.kubernetes.io/id: '1'
   519  functionConfig:
   520    apiVersion: example.com/v1
   521    kind: Example
   522    metadata:
   523      name: foo
   524      annotations:
   525        config.kubernetes.io/path: 'foo/bar.yaml'
   526  `,
   527  			},
   528  			functionConfig: `
   529  apiVersion: example.com/v1
   530  kind: Example
   531  metadata:
   532    name: foo
   533    annotations:
   534      config.kubernetes.io/path: 'foo/bar.yaml'
   535  `,
   536  			input: []string{
   537  				// this should not be in scope
   538  				`
   539  apiVersion: apps/v1
   540  kind: Deployment
   541  metadata:
   542    name: deployment-foo
   543    annotations:
   544      config.kubernetes.io/path: 'baz/bar/d.yaml'
   545  `,
   546  				// this should be in scope
   547  				`
   548  apiVersion: v1
   549  kind: Service
   550  metadata:
   551    name: service-foo
   552    annotations:
   553      config.kubernetes.io/path: 'foo/bar/s.yaml'
   554  `},
   555  			expectedOutput: []string{
   556  				`
   557  apiVersion: v1
   558  kind: Service
   559  metadata:
   560    name: service-foo
   561    annotations:
   562      config.kubernetes.io/path: 'foo/bar/s.yaml'
   563      new: annotation
   564      internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
   565  `, `
   566  apiVersion: apps/v1
   567  kind: Deployment
   568  metadata:
   569    name: deployment-foo
   570    annotations:
   571      config.kubernetes.io/path: 'baz/bar/d.yaml'
   572  `,
   573  			},
   574  		},
   575  
   576  		// verify functions without file path annotation are not scoped to functions
   577  		{
   578  			name: "scope resources by directory resources missing path",
   579  			run: testRun{
   580  				expectedInput: `apiVersion: config.kubernetes.io/v1
   581  kind: ResourceList
   582  items:
   583  - apiVersion: v1
   584    kind: Service
   585    metadata:
   586      name: service-foo
   587      annotations:
   588        config.kubernetes.io/path: 'foo/bar/s.yaml'
   589        internal.config.kubernetes.io/id: '1'
   590        config.k8s.io/id: '1'
   591  functionConfig:
   592    apiVersion: example.com/v1
   593    kind: Example
   594    metadata:
   595      name: foo
   596      annotations:
   597        config.kubernetes.io/path: 'foo/bar.yaml'
   598  `,
   599  				output: `apiVersion: config.kubernetes.io/v1
   600  kind: ResourceList
   601  items:
   602  - apiVersion: v1
   603    kind: Service
   604    metadata:
   605      name: service-foo
   606      annotations:
   607        config.kubernetes.io/path: 'foo/bar/s.yaml'
   608        new: annotation
   609        internal.config.kubernetes.io/id: '1'
   610  functionConfig:
   611    apiVersion: example.com/v1
   612    kind: Example
   613    metadata:
   614      name: foo
   615      annotations:
   616        config.kubernetes.io/path: 'foo/bar.yaml'
   617  `,
   618  			},
   619  			functionConfig: `
   620  apiVersion: example.com/v1
   621  kind: Example
   622  metadata:
   623    name: foo
   624    annotations:
   625      config.kubernetes.io/path: 'foo/bar.yaml'
   626  `,
   627  			input: []string{
   628  				// this should not be in scope
   629  				`
   630  apiVersion: apps/v1
   631  kind: Deployment
   632  metadata:
   633    name: deployment-foo
   634  `,
   635  				// this should be in scope
   636  				`
   637  apiVersion: v1
   638  kind: Service
   639  metadata:
   640    name: service-foo
   641    annotations:
   642      config.kubernetes.io/path: 'foo/bar/s.yaml'
   643  `},
   644  			expectedOutput: []string{
   645  				`
   646  apiVersion: v1
   647  kind: Service
   648  metadata:
   649    name: service-foo
   650    annotations:
   651      config.kubernetes.io/path: 'foo/bar/s.yaml'
   652      new: annotation
   653      internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
   654  `, `
   655  apiVersion: apps/v1
   656  kind: Deployment
   657  metadata:
   658    name: deployment-foo
   659  `,
   660  			},
   661  		},
   662  
   663  		// verify the functions can see all resources if global scope is set
   664  		{
   665  			name:     "scope resources global",
   666  			instance: FunctionFilter{GlobalScope: true},
   667  			run: testRun{
   668  				expectedInput: `apiVersion: config.kubernetes.io/v1
   669  kind: ResourceList
   670  items:
   671  - apiVersion: apps/v1
   672    kind: Deployment
   673    metadata:
   674      name: deployment-foo
   675      annotations:
   676        config.kubernetes.io/path: 'baz/bar/d.yaml'
   677        internal.config.kubernetes.io/id: '1'
   678        config.k8s.io/id: '1'
   679  - apiVersion: v1
   680    kind: Service
   681    metadata:
   682      name: service-foo
   683      annotations:
   684        config.kubernetes.io/path: 'foo/bar/s.yaml'
   685        internal.config.kubernetes.io/id: '2'
   686        config.k8s.io/id: '2'
   687  functionConfig:
   688    apiVersion: example.com/v1
   689    kind: Example
   690    metadata:
   691      name: foo
   692      annotations:
   693        config.kubernetes.io/path: 'foo/bar.yaml'
   694  `,
   695  				output: `apiVersion: config.kubernetes.io/v1
   696  kind: ResourceList
   697  items:
   698  - apiVersion: apps/v1
   699    kind: Deployment
   700    metadata:
   701      name: deployment-foo
   702      annotations:
   703        config.kubernetes.io/path: 'baz/bar/d.yaml'
   704        internal.config.kubernetes.io/id: '1'
   705  - apiVersion: v1
   706    kind: Service
   707    metadata:
   708      name: service-foo
   709      annotations:
   710        config.kubernetes.io/path: 'foo/bar/s.yaml'
   711        new: annotation
   712        config.k8s.io/id: '2'
   713  functionConfig:
   714    apiVersion: example.com/v1
   715    kind: Example
   716    metadata:
   717      name: foo
   718      annotations:
   719        config.kubernetes.io/path: 'foo/bar.yaml'
   720  `,
   721  			},
   722  			functionConfig: `
   723  apiVersion: example.com/v1
   724  kind: Example
   725  metadata:
   726    name: foo
   727    annotations:
   728      config.kubernetes.io/path: 'foo/bar.yaml'
   729  `,
   730  			input: []string{
   731  				`
   732  apiVersion: apps/v1
   733  kind: Deployment
   734  metadata:
   735    name: deployment-foo
   736    annotations:
   737      config.kubernetes.io/path: 'baz/bar/d.yaml'
   738  `,
   739  				`
   740  apiVersion: v1
   741  kind: Service
   742  metadata:
   743    name: service-foo
   744    annotations:
   745      config.kubernetes.io/path: 'foo/bar/s.yaml'
   746  `},
   747  			expectedOutput: []string{
   748  				`
   749  apiVersion: apps/v1
   750  kind: Deployment
   751  metadata:
   752    name: deployment-foo
   753    annotations:
   754      config.kubernetes.io/path: 'baz/bar/d.yaml'
   755      internal.config.kubernetes.io/path: 'baz/bar/d.yaml'
   756  `, `
   757  apiVersion: v1
   758  kind: Service
   759  metadata:
   760    name: service-foo
   761    annotations:
   762      config.kubernetes.io/path: 'foo/bar/s.yaml'
   763      new: annotation
   764      internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
   765  `,
   766  			},
   767  		},
   768  
   769  		{
   770  			name: "scope no resources",
   771  			run: testRun{
   772  				expectedInput: `apiVersion: config.kubernetes.io/v1
   773  kind: ResourceList
   774  items: []
   775  functionConfig:
   776    apiVersion: example.com/v1
   777    kind: Example
   778    metadata:
   779      name: foo
   780      annotations:
   781        config.kubernetes.io/path: 'foo/bar.yaml'
   782  `,
   783  				output: `apiVersion: config.kubernetes.io/v1
   784  kind: ResourceList
   785  items: []
   786  functionConfig:
   787    apiVersion: example.com/v1
   788    kind: Example
   789    metadata:
   790      name: foo
   791      annotations:
   792        config.kubernetes.io/path: 'foo/bar.yaml'
   793  `,
   794  			},
   795  			functionConfig: `
   796  apiVersion: example.com/v1
   797  kind: Example
   798  metadata:
   799    name: foo
   800    annotations:
   801      config.kubernetes.io/path: 'foo/bar.yaml'
   802  `,
   803  			input: []string{
   804  				// these should not be in scope
   805  				`
   806  apiVersion: apps/v1
   807  kind: Deployment
   808  metadata:
   809    name: deployment-foo
   810    annotations:
   811      config.kubernetes.io/path: 'baz/bar/d.yaml'
   812  `, `
   813  apiVersion: v1
   814  kind: Service
   815  metadata:
   816    name: service-foo
   817    annotations:
   818      config.kubernetes.io/path: 'biz/bar/s.yaml'
   819  `},
   820  			expectedOutput: []string{
   821  				`
   822  apiVersion: apps/v1
   823  kind: Deployment
   824  metadata:
   825    name: deployment-foo
   826    annotations:
   827      config.kubernetes.io/path: 'baz/bar/d.yaml'
   828  `, `
   829  apiVersion: v1
   830  kind: Service
   831  metadata:
   832    name: service-foo
   833    annotations:
   834      config.kubernetes.io/path: 'biz/bar/s.yaml'
   835  `,
   836  			},
   837  		},
   838  
   839  		{
   840  			name: "scope functions dir",
   841  			run: testRun{
   842  				expectedInput: `apiVersion: config.kubernetes.io/v1
   843  kind: ResourceList
   844  items:
   845  - apiVersion: v1
   846    kind: Service
   847    metadata:
   848      name: service-foo
   849      annotations:
   850        config.kubernetes.io/path: 'foo/bar/s.yaml'
   851        internal.config.kubernetes.io/id: '1'
   852        config.k8s.io/id: '1'
   853  functionConfig:
   854    apiVersion: example.com/v1
   855    kind: Example
   856    metadata:
   857      name: foo
   858      annotations:
   859        config.kubernetes.io/path: 'foo/functions/bar.yaml'
   860  `,
   861  				output: `apiVersion: config.kubernetes.io/v1
   862  kind: ResourceList
   863  items:
   864  - apiVersion: v1
   865    kind: Service
   866    metadata:
   867      name: service-foo
   868      annotations:
   869        config.kubernetes.io/path: 'foo/bar/s.yaml'
   870        internal.config.kubernetes.io/id: '1'
   871        new: annotation
   872        internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
   873  functionConfig:
   874    apiVersion: example.com/v1
   875    kind: Example
   876    metadata:
   877      name: foo
   878      annotations:
   879        config.kubernetes.io/path: 'foo/functions/bar.yaml'
   880  `,
   881  			},
   882  			functionConfig: `
   883  apiVersion: example.com/v1
   884  kind: Example
   885  metadata:
   886    name: foo
   887    annotations:
   888      config.kubernetes.io/path: 'foo/functions/bar.yaml'
   889  `,
   890  			input: []string{
   891  				// this should not be in scope
   892  				`
   893  apiVersion: apps/v1
   894  kind: Deployment
   895  metadata:
   896    name: deployment-foo
   897    annotations:
   898      config.kubernetes.io/path: 'baz/bar/d.yaml'
   899  `,
   900  				// this should be in scope
   901  				`
   902  apiVersion: v1
   903  kind: Service
   904  metadata:
   905    name: service-foo
   906    annotations:
   907      config.kubernetes.io/path: 'foo/bar/s.yaml'
   908  `},
   909  			expectedOutput: []string{
   910  				`
   911  apiVersion: v1
   912  kind: Service
   913  metadata:
   914    name: service-foo
   915    annotations:
   916      config.kubernetes.io/path: 'foo/bar/s.yaml'
   917      new: annotation
   918      internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
   919  `, `
   920  apiVersion: apps/v1
   921  kind: Deployment
   922  metadata:
   923    name: deployment-foo
   924    annotations:
   925      config.kubernetes.io/path: 'baz/bar/d.yaml'
   926  `,
   927  			},
   928  		},
   929  
   930  		{
   931  			name: "copy comments",
   932  			run: testRun{
   933  				expectedInput: `apiVersion: config.kubernetes.io/v1
   934  kind: ResourceList
   935  items:
   936  - apiVersion: apps/v1
   937    kind: Deployment
   938    metadata:
   939      name: deployment-foo
   940      annotations:
   941        config.kubernetes.io/path: 'foo/b.yaml'
   942        internal.config.kubernetes.io/id: '1'
   943        config.k8s.io/id: '1'
   944  - apiVersion: v1
   945    kind: Service
   946    metadata:
   947      name: service-foo # name comment
   948      annotations:
   949        config.kubernetes.io/path: 'foo/a.yaml'
   950        internal.config.kubernetes.io/id: '2'
   951        config.k8s.io/id: '2'
   952  functionConfig:
   953    apiVersion: example.com/v1
   954    kind: Example
   955    metadata:
   956      name: foo
   957      annotations:
   958        config.kubernetes.io/path: 'foo/f.yaml'
   959  `,
   960  				// delete the comment
   961  				output: `apiVersion: config.kubernetes.io/v1
   962  kind: ResourceList
   963  items:
   964  - apiVersion: apps/v1
   965    kind: Deployment
   966    metadata:
   967      name: deployment-foo
   968      annotations:
   969        config.kubernetes.io/path: 'foo/b.yaml'
   970        internal.config.kubernetes.io/id: '1'
   971  - apiVersion: v1
   972    kind: Service
   973    metadata:
   974      name: service-foo
   975      annotations:
   976        config.kubernetes.io/path: 'foo/a.yaml'
   977        internal.config.kubernetes.io/id: '2'
   978        new: annotation
   979  functionConfig:
   980    apiVersion: example.com/v1
   981    kind: Example
   982    metadata:
   983      name: foo
   984      annotations:
   985        config.kubernetes.io/path: 'foo/f.yaml'
   986  `,
   987  			},
   988  			functionConfig: `
   989  apiVersion: example.com/v1
   990  kind: Example
   991  metadata:
   992    name: foo
   993    annotations:
   994      config.kubernetes.io/path: 'foo/f.yaml'
   995  `,
   996  			input: []string{
   997  				`
   998  apiVersion: apps/v1
   999  kind: Deployment
  1000  metadata:
  1001    name: deployment-foo
  1002    annotations:
  1003      config.kubernetes.io/path: 'foo/b.yaml'
  1004  `,
  1005  				`
  1006  apiVersion: v1
  1007  kind: Service
  1008  metadata:
  1009    name: service-foo # name comment
  1010    annotations:
  1011      config.kubernetes.io/path: 'foo/a.yaml'
  1012  `},
  1013  			expectedOutput: []string{
  1014  				`
  1015  apiVersion: apps/v1
  1016  kind: Deployment
  1017  metadata:
  1018    name: deployment-foo
  1019    annotations:
  1020      config.kubernetes.io/path: 'foo/b.yaml'
  1021      internal.config.kubernetes.io/path: 'foo/b.yaml'
  1022  `, `
  1023  apiVersion: v1
  1024  kind: Service
  1025  metadata:
  1026    name: service-foo # name comment
  1027    annotations:
  1028      config.kubernetes.io/path: 'foo/a.yaml'
  1029      new: annotation
  1030      internal.config.kubernetes.io/path: 'foo/a.yaml'
  1031  `,
  1032  			},
  1033  		},
  1034  	}
  1035  
  1036  	for i := range tests {
  1037  		tt := tests[i]
  1038  		t.Run(tt.name, func(t *testing.T) {
  1039  			// results file setup
  1040  			if len(tt.expectedResults) > 0 && !tt.noMakeResultsFile {
  1041  				// expect result files to be written -- create a directory for them
  1042  				f, err := os.CreateTemp("", "test-kyaml-*.yaml")
  1043  				if !assert.NoError(t, err) {
  1044  					t.FailNow()
  1045  				}
  1046  				defer os.RemoveAll(f.Name())
  1047  				tt.instance.ResultsFile = f.Name()
  1048  			} else if len(tt.expectedResults) > 0 {
  1049  				// failure case for writing to bad results location
  1050  				tt.instance.ResultsFile = "/not/real/file"
  1051  			}
  1052  
  1053  			// initialize the inputs for the FunctionFilter
  1054  			var inputs []*yaml.RNode
  1055  			for i := range tt.input {
  1056  				node, err := yaml.Parse(tt.input[i])
  1057  				if !assert.NoError(t, err) {
  1058  					t.FailNow()
  1059  				}
  1060  				inputs = append(inputs, node)
  1061  			}
  1062  
  1063  			// run the FunctionFilter
  1064  			tt.run.t = t
  1065  			tt.instance.Run = tt.run.run
  1066  			if tt.functionConfig != "" {
  1067  				fc, err := yaml.Parse(tt.functionConfig)
  1068  				if !assert.NoError(t, err) {
  1069  					t.FailNow()
  1070  				}
  1071  				tt.instance.FunctionConfig = fc
  1072  			}
  1073  			output, err := tt.instance.Filter(inputs)
  1074  			if tt.expectedError != "" {
  1075  				if !assert.Error(t, err) {
  1076  					t.FailNow()
  1077  				}
  1078  				if !assert.Contains(t, err.Error(), tt.expectedError) {
  1079  					t.FailNow()
  1080  				}
  1081  				return
  1082  			}
  1083  
  1084  			// check for saved error
  1085  			if tt.expectedSavedError != "" {
  1086  				if !assert.EqualError(t, tt.instance.exit, tt.expectedSavedError) {
  1087  					t.FailNow()
  1088  				}
  1089  			}
  1090  
  1091  			if !assert.NoError(t, err) {
  1092  				t.FailNow()
  1093  			}
  1094  
  1095  			// verify function output
  1096  			var actual []string
  1097  			for i := range output {
  1098  				s, err := output[i].String()
  1099  				if !assert.NoError(t, err) {
  1100  					t.FailNow()
  1101  				}
  1102  				actual = append(actual, strings.TrimSpace(s))
  1103  			}
  1104  			var expected []string
  1105  			for i := range tt.expectedOutput {
  1106  				expected = append(expected, strings.TrimSpace(tt.expectedOutput[i]))
  1107  			}
  1108  
  1109  			if !assert.Equal(t, expected, actual) {
  1110  				t.FailNow()
  1111  			}
  1112  
  1113  			// verify results files
  1114  			if len(tt.instance.ResultsFile) > 0 {
  1115  				tt.expectedResults = strings.TrimSpace(tt.expectedResults)
  1116  
  1117  				results, err := tt.instance.Results.String()
  1118  				if !assert.NoError(t, err) {
  1119  					t.FailNow()
  1120  				}
  1121  				if !assert.Equal(t, tt.expectedResults, strings.TrimSpace(results)) {
  1122  					t.FailNow()
  1123  				}
  1124  
  1125  				b, err := os.ReadFile(tt.instance.ResultsFile)
  1126  				writtenResults := strings.TrimSpace(string(b))
  1127  				if !assert.NoError(t, err) {
  1128  					t.FailNow()
  1129  				}
  1130  				if !assert.Equal(t, tt.expectedResults, writtenResults) {
  1131  					t.FailNow()
  1132  				}
  1133  			}
  1134  		})
  1135  	}
  1136  }
  1137  
  1138  func Test_GetFunction(t *testing.T) {
  1139  	var tests = []struct {
  1140  		name       string
  1141  		resource   string
  1142  		expectedFn string
  1143  		missingFn  bool
  1144  	}{
  1145  
  1146  		// fn annotation
  1147  		{
  1148  			name: "fn annotation",
  1149  			resource: `
  1150  apiVersion: v1beta1
  1151  kind: Example
  1152  metadata:
  1153    annotations:
  1154      config.kubernetes.io/function: |-
  1155        container:
  1156          image: foo:v1.0.0
  1157  `,
  1158  			expectedFn: `
  1159  container:
  1160    image: foo:v1.0.0`,
  1161  		},
  1162  
  1163  		{
  1164  			name: "storage mounts json style",
  1165  			resource: `
  1166  apiVersion: v1beta1
  1167  kind: Example
  1168  metadata:
  1169    annotations:
  1170      config.kubernetes.io/function: |-
  1171        container:
  1172          image: foo:v1.0.0
  1173          mounts: [ {type: bind, src: /mount/path, dst: /local/}, {src: myvol, dst: /local/, type: volume}, {dst: /local/, type: tmpfs} ]
  1174  `,
  1175  			expectedFn: `
  1176  container:
  1177    image: foo:v1.0.0
  1178    mounts:
  1179    - type: bind
  1180      src: /mount/path
  1181      dst: /local/
  1182    - type: volume
  1183      src: myvol
  1184      dst: /local/
  1185    - type: tmpfs
  1186      dst: /local/
  1187  `,
  1188  		},
  1189  
  1190  		{
  1191  			name: "storage mounts yaml style",
  1192  			resource: `
  1193  apiVersion: v1beta1
  1194  kind: Example
  1195  metadata:
  1196    annotations:
  1197      config.kubernetes.io/function: |-
  1198        container:
  1199          image: foo:v1.0.0
  1200          mounts:
  1201          - src: /mount/path
  1202            type: bind
  1203            dst: /local/
  1204          - dst: /local/
  1205            src: myvol
  1206            type: volume
  1207          - type: tmpfs
  1208            dst: /local/
  1209  `,
  1210  			expectedFn: `
  1211  container:
  1212    image: foo:v1.0.0
  1213    mounts:
  1214    - type: bind
  1215      src: /mount/path
  1216      dst: /local/
  1217    - type: volume
  1218      src: myvol
  1219      dst: /local/
  1220    - type: tmpfs
  1221      dst: /local/
  1222  `,
  1223  		},
  1224  
  1225  		{
  1226  			name: "network",
  1227  			resource: `
  1228  apiVersion: v1beta1
  1229  kind: Example
  1230  metadata:
  1231    annotations:
  1232      config.kubernetes.io/function: |-
  1233        container:
  1234          image: foo:v1.0.0
  1235          network: true
  1236  `,
  1237  			expectedFn: `
  1238  container:
  1239    image: foo:v1.0.0
  1240    network: true
  1241  `,
  1242  		},
  1243  
  1244  		{
  1245  			name: "path with uncorrect position",
  1246  			resource: `
  1247  apiVersion: v1beta1
  1248  kind: Example
  1249  metadata:
  1250    annotations:
  1251      config.kubernetes.io/function: |-
  1252        path: foo
  1253        container:
  1254          image: foo:v1.0.0
  1255  `,
  1256  			missingFn: true,
  1257  		},
  1258  
  1259  		{
  1260  			name: "network with uncorrect position",
  1261  			resource: `
  1262  apiVersion: v1beta1
  1263  kind: Example
  1264  metadata:
  1265    annotations:
  1266      config.kubernetes.io/function: |-
  1267        network: foo
  1268        container:
  1269          image: foo:v1.0.0
  1270  `,
  1271  			missingFn: true,
  1272  		},
  1273  		{
  1274  			name: "typo in function config",
  1275  			resource: `
  1276  apiVersion: v1beta1
  1277  kind: Example
  1278  metadata:
  1279    annotations:
  1280      config.kubernetes.io/function: |-
  1281        containeer:
  1282          image: foo:v1.0.0
  1283  `,
  1284  			missingFn: true,
  1285  		},
  1286  
  1287  		// legacy fn style
  1288  		{
  1289  			name: "legacy fn meta",
  1290  			resource: `
  1291  apiVersion: v1beta1
  1292  kind: Example
  1293  metadata:
  1294    configFn:
  1295        container:
  1296          image: foo:v1.0.0
  1297  `,
  1298  			expectedFn: `
  1299  container:
  1300    image: foo:v1.0.0
  1301  `,
  1302  		},
  1303  
  1304  		// no fn
  1305  		{
  1306  			name: "no fn",
  1307  			resource: `
  1308  apiVersion: v1beta1
  1309  kind: Example
  1310  metadata:
  1311    annotations: {}
  1312  `,
  1313  			missingFn: true,
  1314  		},
  1315  
  1316  		// test network, etc...
  1317  	}
  1318  
  1319  	for i := range tests {
  1320  		tt := tests[i]
  1321  		t.Run(tt.name, func(t *testing.T) {
  1322  			resource := yaml.MustParse(tt.resource)
  1323  			fn, err := GetFunctionSpec(resource)
  1324  			if tt.missingFn {
  1325  				if err == nil && !assert.Nil(t, fn) {
  1326  					t.FailNow()
  1327  				}
  1328  				return
  1329  			}
  1330  			b, err := yaml.Marshal(fn)
  1331  			if !assert.NoError(t, err) {
  1332  				t.FailNow()
  1333  			}
  1334  			if !assert.Equal(t,
  1335  				strings.TrimSpace(tt.expectedFn),
  1336  				strings.TrimSpace(string(b))) {
  1337  				t.FailNow()
  1338  			}
  1339  		})
  1340  	}
  1341  }
  1342  
  1343  func Test_GetContainerNetworkRequired(t *testing.T) {
  1344  	tests := []struct {
  1345  		input    string
  1346  		required bool
  1347  	}{
  1348  		{
  1349  			input: `apiVersion: v1
  1350  kind: Foo
  1351  metadata:
  1352    name: foo
  1353    configFn:
  1354      container:
  1355        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1356        network: true
  1357  `,
  1358  			required: true,
  1359  		},
  1360  		{
  1361  			input: `apiVersion: v1
  1362  kind: Foo
  1363  metadata:
  1364    name: foo
  1365    configFn:
  1366      container:
  1367        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1368        network: false
  1369  `,
  1370  			required: false,
  1371  		},
  1372  		{
  1373  
  1374  			input: `apiVersion: v1
  1375  kind: Foo
  1376  metadata:
  1377    name: foo
  1378    configFn:
  1379      container:
  1380        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1381  `,
  1382  			required: false,
  1383  		},
  1384  		{
  1385  			input: `apiVersion: v1
  1386  kind: Foo
  1387  metadata:
  1388    name: foo
  1389    annotations:
  1390      config.kubernetes.io/function: |
  1391        container:
  1392          image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1393          network: true
  1394  `,
  1395  			required: true,
  1396  		},
  1397  	}
  1398  
  1399  	for _, tc := range tests {
  1400  		cfg, err := yaml.Parse(tc.input)
  1401  		if !assert.NoError(t, err) {
  1402  			t.FailNow()
  1403  		}
  1404  		fn, err := GetFunctionSpec(cfg)
  1405  		if !assert.NoError(t, err) {
  1406  			t.FailNow()
  1407  		}
  1408  		assert.Equal(t, tc.required, fn.Container.Network)
  1409  	}
  1410  }
  1411  
  1412  func Test_StringToStorageMount(t *testing.T) {
  1413  	tests := []struct {
  1414  		in          string
  1415  		expectedOut string
  1416  	}{
  1417  		{
  1418  			in:          "type=bind,src=/tmp/test/,dst=/tmp/source/",
  1419  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/,readonly",
  1420  		},
  1421  		{
  1422  			in:          "type=bind,src=/tmp/test/,dst=/tmp/source/,rw=true",
  1423  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/",
  1424  		},
  1425  		{
  1426  			in:          "type=bind,src=/tmp/test/,dst=/tmp/source/,rw=false",
  1427  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/,readonly",
  1428  		},
  1429  		{
  1430  			in:          "type=bind,src=/tmp/test/,dst=/tmp/source/,rw=",
  1431  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/,readonly",
  1432  		},
  1433  		{
  1434  			in:          "type=tmpfs,src=/tmp/test/,dst=/tmp/source/,rw=invalid",
  1435  			expectedOut: "type=tmpfs,source=/tmp/test/,target=/tmp/source/,readonly",
  1436  		},
  1437  		{
  1438  			in:          "type=tmpfs,src=/tmp/test/,dst=/tmp/source/,rwe=invalid",
  1439  			expectedOut: "type=tmpfs,source=/tmp/test/,target=/tmp/source/,readonly",
  1440  		},
  1441  		{
  1442  			in:          "type=tmpfs,src=/tmp/test/,dst",
  1443  			expectedOut: "type=tmpfs,source=/tmp/test/,target=,readonly",
  1444  		},
  1445  		{
  1446  			in:          "type=bind,source=/tmp/test/,target=/tmp/source/,rw=true",
  1447  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/",
  1448  		},
  1449  		{
  1450  			in:          "type=bind,source=/tmp/test/,target=/tmp/source/",
  1451  			expectedOut: "type=bind,source=/tmp/test/,target=/tmp/source/,readonly",
  1452  		},
  1453  	}
  1454  
  1455  	for _, tc := range tests {
  1456  		s := StringToStorageMount(tc.in)
  1457  		assert.Equal(t, tc.expectedOut, (&s).String())
  1458  	}
  1459  }
  1460  
  1461  func TestContainerEnvGetDockerFlags(t *testing.T) {
  1462  	tests := []struct {
  1463  		input  *ContainerEnv
  1464  		output []string
  1465  	}{
  1466  		{
  1467  			input:  NewContainerEnvFromStringSlice([]string{"foo=bar"}),
  1468  			output: []string{"-e", "LOG_TO_STDERR=true", "-e", "STRUCTURED_RESULTS=true", "-e", "foo=bar"},
  1469  		},
  1470  		{
  1471  			input:  NewContainerEnvFromStringSlice([]string{"foo"}),
  1472  			output: []string{"-e", "LOG_TO_STDERR=true", "-e", "STRUCTURED_RESULTS=true", "-e", "foo"},
  1473  		},
  1474  		{
  1475  			input:  NewContainerEnvFromStringSlice([]string{"foo=bar", "baz"}),
  1476  			output: []string{"-e", "LOG_TO_STDERR=true", "-e", "STRUCTURED_RESULTS=true", "-e", "foo=bar", "-e", "baz"},
  1477  		},
  1478  		{
  1479  			input:  NewContainerEnv(),
  1480  			output: []string{"-e", "LOG_TO_STDERR=true", "-e", "STRUCTURED_RESULTS=true"},
  1481  		},
  1482  	}
  1483  
  1484  	for _, tc := range tests {
  1485  		flags := tc.input.GetDockerFlags()
  1486  		assert.Equal(t, tc.output, flags)
  1487  	}
  1488  }
  1489  
  1490  func TestGetContainerEnv(t *testing.T) {
  1491  	tests := []struct {
  1492  		input    string
  1493  		expected ContainerEnv
  1494  	}{
  1495  		{
  1496  			input: `
  1497  apiVersion: v1
  1498  kind: Foo
  1499  metadata:
  1500    name: foo
  1501    configFn:
  1502      container:
  1503        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1504        envs:
  1505        - foo=bar
  1506  `,
  1507  			expected: *NewContainerEnvFromStringSlice([]string{"foo=bar"}),
  1508  		},
  1509  		{
  1510  			input: `
  1511  apiVersion: v1
  1512  kind: Foo
  1513  metadata:
  1514    name: foo
  1515    configFn:
  1516      container:
  1517        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1518        envs:
  1519        - foo=bar
  1520        - baz
  1521  `,
  1522  			expected: *NewContainerEnvFromStringSlice([]string{"foo=bar", "baz"}),
  1523  		},
  1524  		{
  1525  			input: `
  1526  apiVersion: v1
  1527  kind: Foo
  1528  metadata:
  1529    name: foo
  1530    configFn:
  1531      container:
  1532        image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
  1533        envs:
  1534        - KUBECONFIG
  1535  `,
  1536  			expected: *NewContainerEnvFromStringSlice([]string{"KUBECONFIG"}),
  1537  		},
  1538  	}
  1539  
  1540  	for _, tc := range tests {
  1541  		cfg, err := yaml.Parse(tc.input)
  1542  		if !assert.NoError(t, err) {
  1543  			t.FailNow()
  1544  		}
  1545  		fn, err := GetFunctionSpec(cfg)
  1546  		if !assert.NoError(t, err) {
  1547  			t.FailNow()
  1548  		}
  1549  		assert.Equal(t, tc.expected, *NewContainerEnvFromStringSlice(fn.Container.Env))
  1550  	}
  1551  }
  1552  

View as plain text