...

Source file src/sigs.k8s.io/kustomize/api/internal/target/kusttarget_test.go

Documentation: sigs.k8s.io/kustomize/api/internal/target

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package target_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"sigs.k8s.io/kustomize/api/ifc"
    16  	. "sigs.k8s.io/kustomize/api/internal/target"
    17  	"sigs.k8s.io/kustomize/api/internal/utils"
    18  	"sigs.k8s.io/kustomize/api/pkg/loader"
    19  	"sigs.k8s.io/kustomize/api/provider"
    20  	"sigs.k8s.io/kustomize/api/resmap"
    21  	"sigs.k8s.io/kustomize/api/resource"
    22  	kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
    23  	"sigs.k8s.io/kustomize/api/types"
    24  )
    25  
    26  // KustTarget is primarily tested in the krusty package with
    27  // high level tests.
    28  
    29  func TestLoadKustFile(t *testing.T) {
    30  	for name, test := range map[string]struct {
    31  		fileNames            []string
    32  		kustFileName, errMsg string
    33  	}{
    34  		"missing": {
    35  			fileNames: []string{"kustomization"},
    36  			errMsg:    `unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization' in directory '/'`,
    37  		},
    38  		"multiple": {
    39  			fileNames: []string{"kustomization.yaml", "Kustomization"},
    40  			errMsg: `Found multiple kustomization files under: /
    41  `,
    42  		},
    43  		"valid": {
    44  			fileNames:    []string{"kustomization.yml", "kust"},
    45  			kustFileName: "kustomization.yml",
    46  		},
    47  	} {
    48  		t.Run(name, func(t *testing.T) {
    49  			th := kusttest_test.MakeHarness(t)
    50  			fSys := th.GetFSys()
    51  			for _, file := range test.fileNames {
    52  				require.NoError(t, fSys.WriteFile(file, []byte(fmt.Sprintf("namePrefix: test-%s", file))))
    53  			}
    54  
    55  			content, fileName, err := LoadKustFile(loader.NewFileLoaderAtCwd(fSys))
    56  			if test.kustFileName != "" {
    57  				require.NoError(t, err)
    58  				require.Equal(t, fmt.Sprintf("namePrefix: test-%s", test.kustFileName), string(content))
    59  				require.Equal(t, test.kustFileName, fileName)
    60  			} else {
    61  				require.EqualError(t, err, test.errMsg)
    62  			}
    63  		})
    64  	}
    65  }
    66  
    67  func TestLoad(t *testing.T) {
    68  	th := kusttest_test.MakeHarness(t)
    69  	expectedTypeMeta := types.TypeMeta{
    70  		APIVersion: "kustomize.config.k8s.io/v1beta1",
    71  		Kind:       "Kustomization",
    72  	}
    73  
    74  	testCases := map[string]struct {
    75  		errContains string
    76  		content     string
    77  		k           types.Kustomization
    78  	}{
    79  		"empty": {
    80  			// no content
    81  			k: types.Kustomization{
    82  				TypeMeta: expectedTypeMeta,
    83  			},
    84  			errContains: "kustomization.yaml is empty",
    85  		},
    86  		"nonsenseLatin": {
    87  			errContains: "found a tab character that violates indentation",
    88  			content: `
    89  		Lorem ipsum dolor sit amet, consectetur
    90  		adipiscing elit, sed do eiusmod tempor
    91  		incididunt ut labore et dolore magna aliqua.
    92  		Ut enim ad minim veniam, quis nostrud
    93  		exercitation ullamco laboris nisi ut
    94  		aliquip ex ea commodo consequat.
    95  		`,
    96  		},
    97  		"simple": {
    98  			content: `
    99  commonLabels:
   100    app: nginx
   101  `,
   102  			k: types.Kustomization{
   103  				TypeMeta:     expectedTypeMeta,
   104  				CommonLabels: map[string]string{"app": "nginx"},
   105  			},
   106  		},
   107  		"commented": {
   108  			content: `
   109  # Licensed to the Blah Blah Software Foundation
   110  # ...
   111  # yada yada yada.
   112  
   113  commonLabels:
   114   app: nginx
   115  `,
   116  			k: types.Kustomization{
   117  				TypeMeta:     expectedTypeMeta,
   118  				CommonLabels: map[string]string{"app": "nginx"},
   119  			},
   120  		},
   121  	}
   122  
   123  	kt := makeKustTargetWithRf(
   124  		t, th.GetFSys(), "/", provider.NewDefaultDepProvider())
   125  	for tn, tc := range testCases {
   126  		t.Run(tn, func(t *testing.T) {
   127  			th.WriteK("/", tc.content)
   128  			err := kt.Load()
   129  			if tc.errContains != "" {
   130  				require.NotNilf(t, err, "expected error containing: `%s`", tc.errContains)
   131  				require.Contains(t, err.Error(), tc.errContains)
   132  			} else {
   133  				require.Nilf(t, err, "got error: %v", err)
   134  				k := kt.Kustomization()
   135  				require.Condition(t, func() bool {
   136  					return reflect.DeepEqual(tc.k, k)
   137  				}, "expected %v, got %v", tc.k, k)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestMakeCustomizedResMap(t *testing.T) {
   144  	th := kusttest_test.MakeHarness(t)
   145  	th.WriteK("/whatever", `namePrefix: foo-
   146  nameSuffix: -bar
   147  namespace: ns1
   148  commonLabels:
   149    app: nginx
   150  commonAnnotations:
   151    note: This is a test annotation
   152  resources:
   153    - deployment.yaml
   154    - namespace.yaml
   155  generatorOptions:
   156    disableNameSuffixHash: false
   157  configMapGenerator:
   158  - name: literalConfigMap
   159    literals:
   160    - DB_USERNAME=admin
   161    - DB_PASSWORD=somepw
   162  secretGenerator:
   163  - name: secret
   164    literals:
   165      - DB_USERNAME=admin
   166      - DB_PASSWORD=somepw
   167    type: Opaque
   168  patchesJson6902:
   169  - target:
   170      group: apps
   171      version: v1
   172      kind: Deployment
   173      name: dply1
   174    path: jsonpatch.json
   175  `)
   176  	th.WriteF("/whatever/deployment.yaml", `
   177  apiVersion: apps/v1
   178  metadata:
   179    name: dply1
   180  kind: Deployment
   181  `)
   182  	th.WriteF("/whatever/namespace.yaml", `
   183  apiVersion: v1
   184  kind: Namespace
   185  metadata:
   186    name: ns1
   187  `)
   188  	th.WriteF("/whatever/jsonpatch.json", `[
   189      {"op": "add", "path": "/spec/replica", "value": "3"}
   190  ]`)
   191  
   192  	pvd := provider.NewDefaultDepProvider()
   193  	resFactory := pvd.GetResourceFactory()
   194  
   195  	resources := []*resource.Resource{
   196  		resFactory.FromMapWithName("dply1", map[string]interface{}{
   197  			"apiVersion": "apps/v1",
   198  			"kind":       "Deployment",
   199  			"metadata": map[string]interface{}{
   200  				"name":      "foo-dply1-bar",
   201  				"namespace": "ns1",
   202  				"labels": map[string]interface{}{
   203  					"app": "nginx",
   204  				},
   205  				"annotations": map[string]interface{}{
   206  					"note": "This is a test annotation",
   207  				},
   208  			},
   209  			"spec": map[string]interface{}{
   210  				"replica": "3",
   211  				"selector": map[string]interface{}{
   212  					"matchLabels": map[string]interface{}{
   213  						"app": "nginx",
   214  					},
   215  				},
   216  				"template": map[string]interface{}{
   217  					"metadata": map[string]interface{}{
   218  						"annotations": map[string]interface{}{
   219  							"note": "This is a test annotation",
   220  						},
   221  						"labels": map[string]interface{}{
   222  							"app": "nginx",
   223  						},
   224  					},
   225  				},
   226  			},
   227  		}),
   228  		resFactory.FromMapWithName("ns1", map[string]interface{}{
   229  			"apiVersion": "v1",
   230  			"kind":       "Namespace",
   231  			"metadata": map[string]interface{}{
   232  				"name": "ns1",
   233  				"labels": map[string]interface{}{
   234  					"app": "nginx",
   235  				},
   236  				"annotations": map[string]interface{}{
   237  					"note": "This is a test annotation",
   238  				},
   239  			},
   240  		}),
   241  		resFactory.FromMapWithName("literalConfigMap",
   242  			map[string]interface{}{
   243  				"apiVersion": "v1",
   244  				"kind":       "ConfigMap",
   245  				"metadata": map[string]interface{}{
   246  					"name":      "foo-literalConfigMap-bar-g5f6t456f5",
   247  					"namespace": "ns1",
   248  					"labels": map[string]interface{}{
   249  						"app": "nginx",
   250  					},
   251  					"annotations": map[string]interface{}{
   252  						"note": "This is a test annotation",
   253  					},
   254  				},
   255  				"data": map[string]interface{}{
   256  					"DB_USERNAME": "admin",
   257  					"DB_PASSWORD": "somepw",
   258  				},
   259  			}),
   260  		resFactory.FromMapWithName("secret",
   261  			map[string]interface{}{
   262  				"apiVersion": "v1",
   263  				"kind":       "Secret",
   264  				"metadata": map[string]interface{}{
   265  					"name":      "foo-secret-bar-82c2g5f8f6",
   266  					"namespace": "ns1",
   267  					"labels": map[string]interface{}{
   268  						"app": "nginx",
   269  					},
   270  					"annotations": map[string]interface{}{
   271  						"note": "This is a test annotation",
   272  					},
   273  				},
   274  				"type": ifc.SecretTypeOpaque,
   275  				"data": map[string]interface{}{
   276  					"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
   277  					"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
   278  				},
   279  			}),
   280  	}
   281  
   282  	expected := resmap.New()
   283  	for _, r := range resources {
   284  		if err := expected.Append(r); err != nil {
   285  			t.Fatalf("unexpected error %v", err)
   286  		}
   287  	}
   288  	expected.RemoveBuildAnnotations()
   289  	expYaml, err := expected.AsYaml()
   290  	require.NoError(t, err)
   291  
   292  	kt := makeKustTargetWithRf(t, th.GetFSys(), "/whatever", pvd)
   293  	require.NoError(t, kt.Load())
   294  	actual, err := kt.MakeCustomizedResMap()
   295  	require.NoError(t, err)
   296  	actual.RemoveBuildAnnotations()
   297  	actYaml, err := actual.AsYaml()
   298  	require.NoError(t, err)
   299  	assert.Equal(t, string(expYaml), string(actYaml))
   300  }
   301  
   302  func TestConfigurationsOverrideDefault(t *testing.T) {
   303  	th := kusttest_test.MakeHarness(t)
   304  	th.WriteK("/merge-config", `namePrefix: foo-
   305  nameSuffix: -bar
   306  namespace: ns1
   307  resources:
   308    - deployment.yaml
   309    - config.yaml
   310    - secret.yaml
   311  configurations:
   312    - name-prefix-rules.yaml
   313    - name-suffix-rules.yaml
   314  `)
   315  	th.WriteF("/merge-config/name-prefix-rules.yaml", `
   316  namePrefix:
   317  - path: metadata/name
   318    group: apps
   319    version: v1
   320    kind: Deployment
   321  - path: metadata/name
   322    version: v1
   323    kind: Secret
   324  `)
   325  	th.WriteF("/merge-config/name-suffix-rules.yaml", `
   326  nameSuffix:
   327  - path: metadata/name
   328    version: v1
   329    kind: ConfigMap
   330  - path: metadata/name
   331    group: apps
   332    version: v1
   333    kind: Deployment
   334  `)
   335  	th.WriteF("/merge-config/deployment.yaml", `
   336  apiVersion: apps/v1
   337  metadata:
   338    name: deployment1
   339  kind: Deployment
   340  `)
   341  	th.WriteF("/merge-config/config.yaml", `
   342  apiVersion: v1
   343  kind: ConfigMap
   344  metadata:
   345    name: config
   346  `)
   347  	th.WriteF("/merge-config/secret.yaml", `
   348  apiVersion: v1
   349  kind: Secret
   350  metadata:
   351    name: secret
   352  `)
   353  
   354  	pvd := provider.NewDefaultDepProvider()
   355  	resFactory := pvd.GetResourceFactory()
   356  
   357  	resources := []*resource.Resource{
   358  		resFactory.FromMapWithName("deployment1", map[string]interface{}{
   359  			"apiVersion": "apps/v1",
   360  			"kind":       "Deployment",
   361  			"metadata": map[string]interface{}{
   362  				"name":      "foo-deployment1-bar",
   363  				"namespace": "ns1",
   364  			},
   365  		}), resFactory.FromMapWithName("config", map[string]interface{}{
   366  			"apiVersion": "v1",
   367  			"kind":       "ConfigMap",
   368  			"metadata": map[string]interface{}{
   369  				"name":      "config-bar",
   370  				"namespace": "ns1",
   371  			},
   372  		}), resFactory.FromMapWithName("secret", map[string]interface{}{
   373  			"apiVersion": "v1",
   374  			"kind":       "Secret",
   375  			"metadata": map[string]interface{}{
   376  				"name":      "foo-secret",
   377  				"namespace": "ns1",
   378  			},
   379  		}),
   380  	}
   381  
   382  	expected := resmap.New()
   383  	for _, r := range resources {
   384  		err := expected.Append(r)
   385  		require.NoError(t, err)
   386  	}
   387  	expected.RemoveBuildAnnotations()
   388  	expYaml, err := expected.AsYaml()
   389  	require.NoError(t, err)
   390  
   391  	kt := makeKustTargetWithRf(t, th.GetFSys(), "/merge-config", pvd)
   392  	require.NoError(t, kt.Load())
   393  	actual, err := kt.MakeCustomizedResMap()
   394  	require.NoError(t, err)
   395  	actual.RemoveBuildAnnotations()
   396  	actYaml, err := actual.AsYaml()
   397  	require.NoError(t, err)
   398  	require.Equal(t, string(expYaml), string(actYaml))
   399  }
   400  
   401  func TestDuplicateExternalGeneratorsForbidden(t *testing.T) {
   402  	th := kusttest_test.MakeHarness(t)
   403  	th.WriteK("/generator", `generators:
   404  - |-
   405    apiVersion: generators.example/v1
   406    kind: ManifestGenerator
   407    metadata:
   408      name: ManifestGenerator
   409      annotations:
   410        config.kubernetes.io/function: |
   411          container:
   412            image: ManifestGenerator:latest
   413    spec:
   414      image: 'someimage:12345'
   415      configPath: config.json
   416  - |-
   417    apiVersion: generators.example/v1
   418    kind: ManifestGenerator
   419    metadata:
   420      name: ManifestGenerator
   421      annotations:
   422        config.kubernetes.io/function: |
   423          container:
   424            image: ManifestGenerator:latest
   425    spec:
   426      image: 'someimage:12345'
   427      configPath: another_config.json
   428  `)
   429  	_, err := makeAndLoadKustTarget(t, th.GetFSys(), "/generator").AccumulateTarget()
   430  	require.Error(t, err)
   431  	assert.Contains(t, err.Error(), "may not add resource with an already registered id: ManifestGenerator.v1.generators.example/ManifestGenerator")
   432  }
   433  
   434  func TestDuplicateExternalTransformersForbidden(t *testing.T) {
   435  	th := kusttest_test.MakeHarness(t)
   436  	th.WriteK("/transformer", `transformers:
   437  - |-
   438    apiVersion: transformers.example.co/v1
   439    kind: ValueAnnotator
   440    metadata:
   441      name: notImportantHere
   442      annotations:
   443        config.kubernetes.io/function: |
   444          container:
   445            image: example.docker.com/my-functions/valueannotator:1.0.0
   446    value: 'pass'
   447  - |-
   448    apiVersion: transformers.example.co/v1
   449    kind: ValueAnnotator
   450    metadata:
   451      name: notImportantHere
   452      annotations:
   453        config.kubernetes.io/function: |
   454          container:
   455            image: example.docker.com/my-functions/valueannotator:1.0.0
   456    value: 'fail'
   457  `)
   458  	_, err := makeAndLoadKustTarget(t, th.GetFSys(), "/transformer").AccumulateTarget()
   459  	require.Error(t, err)
   460  	assert.Contains(t, err.Error(), "may not add resource with an already registered id: ValueAnnotator.v1.transformers.example.co/notImportantHere")
   461  }
   462  
   463  func TestErrorMessageForMalformedYAML(t *testing.T) {
   464  	// These testcases verify behavior for the scenario described in
   465  	// https://github.com/kubernetes-sigs/kustomize/issues/5540 .
   466  
   467  	testcases := map[string]struct {
   468  		loaderNewReturnsError error
   469  		shouldShowLoadError   bool
   470  	}{
   471  		"shouldShowLoadError": {
   472  			loaderNewReturnsError: utils.NewErrTimeOut(time.Second, "git init"),
   473  			shouldShowLoadError:   true,
   474  		},
   475  		"shouldNotShowLoadError": {
   476  			loaderNewReturnsError: NewErrMissingKustomization("/should-fail/resources.yaml"),
   477  			shouldShowLoadError:   false,
   478  		},
   479  	}
   480  
   481  	th := kusttest_test.MakeHarness(t)
   482  	th.WriteF("/should-fail/kustomization.yaml", `resources:
   483  - resources.yaml
   484  `)
   485  	th.WriteF("/should-fail/resources.yaml", `<!DOCTYPE html>
   486  <html class="html-devise-layout ui-light-gray" lang="en">
   487  <head prefix="og: http://ogp.me/ns#">
   488  <meta charset="utf-8">
   489  `)
   490  
   491  	for name, tc := range testcases {
   492  		t.Run(name, func(subT *testing.T) {
   493  			ldrWrapper := func(baseLoader ifc.Loader) ifc.Loader {
   494  				return loaderNewThrowsError{
   495  					baseLoader:      baseLoader,
   496  					newReturnsError: tc.loaderNewReturnsError,
   497  				}
   498  			}
   499  			_, err := makeAndLoadKustTargetWithLoaderOverride(t, th.GetFSys(), "/should-fail", ldrWrapper).AccumulateTarget()
   500  			require.Error(t, err)
   501  			errString := err.Error()
   502  			assert.Contains(t, errString, "accumulating resources from 'resources.yaml'")
   503  			assert.Contains(t, errString, "MalformedYAMLError: yaml: line 3: mapping values are not allowed in this context")
   504  			if tc.shouldShowLoadError {
   505  				assert.Regexp(t, `hit \w+ timeout running '`, errString)
   506  			} else {
   507  				assert.NotRegexp(t, `hit \w+ timeout running '`, errString)
   508  			}
   509  		})
   510  	}
   511  }
   512  
   513  // loaderNewReturnsError duplicates baseLoader's behavior except
   514  // that New() returns the specified error.
   515  type loaderNewThrowsError struct {
   516  	baseLoader      ifc.Loader
   517  	newReturnsError error
   518  }
   519  
   520  func (l loaderNewThrowsError) Repo() string {
   521  	return l.baseLoader.Repo()
   522  }
   523  
   524  func (l loaderNewThrowsError) Root() string {
   525  	return l.baseLoader.Root()
   526  }
   527  
   528  func (l loaderNewThrowsError) New(_ string) (ifc.Loader, error) {
   529  	return nil, l.newReturnsError
   530  }
   531  
   532  func (l loaderNewThrowsError) Load(location string) ([]byte, error) {
   533  	return l.baseLoader.Load(location) //nolint:wrapcheck // baseLoader's error is sufficient
   534  }
   535  
   536  func (l loaderNewThrowsError) Cleanup() error {
   537  	return l.baseLoader.Cleanup() //nolint:wrapcheck // baseLoader's error is sufficient
   538  }
   539  

View as plain text