...

Source file src/sigs.k8s.io/kustomize/api/krusty/component_test.go

Documentation: sigs.k8s.io/kustomize/api/krusty

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package krusty_test
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"testing"
    10  
    11  	"sigs.k8s.io/kustomize/api/internal/loader"
    12  	"sigs.k8s.io/kustomize/api/konfig"
    13  	kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
    14  )
    15  
    16  type FileGen func(kusttest_test.Harness)
    17  
    18  func writeC(path string, content string) FileGen {
    19  	return func(th kusttest_test.Harness) {
    20  		th.WriteC(path, content)
    21  	}
    22  }
    23  
    24  func writeF(path string, content string) FileGen {
    25  	return func(th kusttest_test.Harness) {
    26  		th.WriteF(path, content)
    27  	}
    28  }
    29  
    30  func writeK(path string, content string) FileGen {
    31  	return func(th kusttest_test.Harness) {
    32  		th.WriteK(path, content)
    33  	}
    34  }
    35  
    36  func writeTestBase(th kusttest_test.Harness) {
    37  	th.WriteK("base", `
    38  resources:
    39  - deploy.yaml
    40  configMapGenerator:
    41  - name: my-configmap
    42    literals:
    43    - testValue=purple
    44    - otherValue=green
    45  `)
    46  	th.WriteF("base/deploy.yaml", `
    47  apiVersion: v1
    48  kind: Deployment
    49  metadata:
    50    name: storefront
    51  spec:
    52    replicas: 1
    53  `)
    54  }
    55  
    56  func writeTestComponent(th kusttest_test.Harness) {
    57  	th.WriteC("comp", `
    58  namePrefix: comp-
    59  replicas:
    60  - name: storefront
    61    count: 3
    62  resources:
    63  - stub.yaml
    64  configMapGenerator:
    65  - name: my-configmap
    66    behavior: merge
    67    literals:
    68    - testValue=blue
    69    - compValue=red
    70  `)
    71  	th.WriteF("comp/stub.yaml", `
    72  apiVersion: v1
    73  kind: Deployment
    74  metadata:
    75    name: stub
    76  spec:
    77    replicas: 1
    78  `)
    79  }
    80  
    81  func writeOverlayProd(th kusttest_test.Harness) {
    82  	th.WriteK("prod", `
    83  resources:
    84  - ../base
    85  - db
    86  
    87  components:
    88  - ../comp
    89  `)
    90  	writeDB(th)
    91  }
    92  
    93  func writeDB(th kusttest_test.Harness) {
    94  	deployment("db", "prod/db")(th)
    95  }
    96  
    97  func deployment(name string, path string) FileGen {
    98  	return writeF(path, fmt.Sprintf(`
    99  apiVersion: v1
   100  kind: Deployment
   101  metadata:
   102    name: %s
   103  spec:
   104    type: Logical
   105  `, name))
   106  }
   107  
   108  func TestComponent(t *testing.T) {
   109  	testCases := map[string]struct {
   110  		input          []FileGen
   111  		runPath        string
   112  		expectedOutput string
   113  	}{
   114  		// Components are inserted into the resource hierarchy as the parent of those
   115  		// resources that come before it in the resources list of the parent Kustomization.
   116  		"basic-component": {
   117  			input:   []FileGen{writeTestBase, writeTestComponent, writeOverlayProd},
   118  			runPath: "prod",
   119  			expectedOutput: `
   120  apiVersion: v1
   121  kind: Deployment
   122  metadata:
   123    name: comp-storefront
   124  spec:
   125    replicas: 3
   126  ---
   127  apiVersion: v1
   128  data:
   129    compValue: red
   130    otherValue: green
   131    testValue: blue
   132  kind: ConfigMap
   133  metadata:
   134    name: comp-my-configmap-97647ckcmg
   135  ---
   136  apiVersion: v1
   137  kind: Deployment
   138  metadata:
   139    name: comp-db
   140  spec:
   141    type: Logical
   142  ---
   143  apiVersion: v1
   144  kind: Deployment
   145  metadata:
   146    name: comp-stub
   147  spec:
   148    replicas: 1
   149  `,
   150  		},
   151  		"multiple-components": {
   152  			input: []FileGen{writeTestBase, writeTestComponent, writeDB,
   153  				writeC("additionalcomp", `
   154  configMapGenerator:
   155  - name: my-configmap
   156    behavior: merge
   157    literals:
   158    - otherValue=orange
   159  `),
   160  				writeK("prod", `
   161  resources:
   162  - ../base
   163  - db
   164  
   165  components:
   166  - ../comp
   167  - ../additionalcomp
   168  `),
   169  			},
   170  			runPath: "prod",
   171  			expectedOutput: `
   172  apiVersion: v1
   173  kind: Deployment
   174  metadata:
   175    name: comp-storefront
   176  spec:
   177    replicas: 3
   178  ---
   179  apiVersion: v1
   180  data:
   181    compValue: red
   182    otherValue: orange
   183    testValue: blue
   184  kind: ConfigMap
   185  metadata:
   186    name: comp-my-configmap-g486mb229k
   187  ---
   188  apiVersion: v1
   189  kind: Deployment
   190  metadata:
   191    name: comp-db
   192  spec:
   193    type: Logical
   194  ---
   195  apiVersion: v1
   196  kind: Deployment
   197  metadata:
   198    name: comp-stub
   199  spec:
   200    replicas: 1
   201  `,
   202  		},
   203  		"nested-components": {
   204  			input: []FileGen{writeTestBase, writeTestComponent, writeDB,
   205  				writeC("additionalcomp", `
   206  components:
   207  - ../comp
   208  configMapGenerator:
   209  - name: my-configmap
   210    behavior: merge
   211    literals:
   212    - otherValue=orange
   213  `),
   214  				writeK("prod", `
   215  resources:
   216  - ../base
   217  - db
   218  
   219  components:
   220  - ../additionalcomp
   221  `),
   222  			},
   223  			runPath: "prod",
   224  			expectedOutput: `
   225  apiVersion: v1
   226  kind: Deployment
   227  metadata:
   228    name: comp-storefront
   229  spec:
   230    replicas: 3
   231  ---
   232  apiVersion: v1
   233  data:
   234    compValue: red
   235    otherValue: orange
   236    testValue: blue
   237  kind: ConfigMap
   238  metadata:
   239    name: comp-my-configmap-g486mb229k
   240  ---
   241  apiVersion: v1
   242  kind: Deployment
   243  metadata:
   244    name: comp-db
   245  spec:
   246    type: Logical
   247  ---
   248  apiVersion: v1
   249  kind: Deployment
   250  metadata:
   251    name: comp-stub
   252  spec:
   253    replicas: 1
   254  `,
   255  		},
   256  		// If a component sets a name prefix on a base, then that base can also be separately included
   257  		// without being affected by the component in another branch of the resource tree
   258  		"basic-component-with-repeated-base": {
   259  			input: []FileGen{writeTestBase, writeTestComponent, writeOverlayProd,
   260  				writeK("repeated", `
   261  resources:
   262  - ../base
   263  - ../prod
   264  `),
   265  			},
   266  			runPath: "repeated",
   267  			expectedOutput: `
   268  apiVersion: v1
   269  kind: Deployment
   270  metadata:
   271    name: storefront
   272  spec:
   273    replicas: 1
   274  ---
   275  apiVersion: v1
   276  data:
   277    otherValue: green
   278    testValue: purple
   279  kind: ConfigMap
   280  metadata:
   281    name: my-configmap-9cd648hm8f
   282  ---
   283  apiVersion: v1
   284  kind: Deployment
   285  metadata:
   286    name: comp-storefront
   287  spec:
   288    replicas: 3
   289  ---
   290  apiVersion: v1
   291  data:
   292    compValue: red
   293    otherValue: green
   294    testValue: blue
   295  kind: ConfigMap
   296  metadata:
   297    name: comp-my-configmap-97647ckcmg
   298  ---
   299  apiVersion: v1
   300  kind: Deployment
   301  metadata:
   302    name: comp-db
   303  spec:
   304    type: Logical
   305  ---
   306  apiVersion: v1
   307  kind: Deployment
   308  metadata:
   309    name: comp-stub
   310  spec:
   311    replicas: 1
   312  `,
   313  		},
   314  		"applying-component-directly-should-be-same-as-kustomization": {
   315  			input: []FileGen{writeTestBase, writeTestComponent,
   316  				writeC("direct-component", `
   317  resources:
   318  - ../base
   319  configMapGenerator:
   320  - name: my-configmap
   321    behavior: merge
   322    literals:
   323    - compValue=red
   324    - testValue=blue
   325  `),
   326  			},
   327  			runPath: "direct-component",
   328  			expectedOutput: `
   329  apiVersion: v1
   330  kind: Deployment
   331  metadata:
   332    name: storefront
   333  spec:
   334    replicas: 1
   335  ---
   336  apiVersion: v1
   337  data:
   338    compValue: red
   339    otherValue: green
   340    testValue: blue
   341  kind: ConfigMap
   342  metadata:
   343    name: my-configmap-97647ckcmg
   344  `,
   345  		},
   346  		"missing-optional-component-api-version": {
   347  			input: []FileGen{writeTestBase, writeOverlayProd,
   348  				writeF("comp/"+konfig.DefaultKustomizationFileName(), `
   349  kind: Component
   350  configMapGenerator:
   351  - name: my-configmap
   352    behavior: merge
   353    literals:
   354    - otherValue=orange
   355  `),
   356  			},
   357  			runPath: "prod",
   358  			expectedOutput: `
   359  apiVersion: v1
   360  kind: Deployment
   361  metadata:
   362    name: storefront
   363  spec:
   364    replicas: 1
   365  ---
   366  apiVersion: v1
   367  data:
   368    otherValue: orange
   369    testValue: purple
   370  kind: ConfigMap
   371  metadata:
   372    name: my-configmap-6hhdg8gkdg
   373  ---
   374  apiVersion: v1
   375  kind: Deployment
   376  metadata:
   377    name: db
   378  spec:
   379    type: Logical
   380  `,
   381  		},
   382  		// See how nameSuffix "-b" is also added to the resources included by "comp-a" because they are in the
   383  		// accumulator when "comp-b" is accumulated.  In practice we could use simple Kustomizations for this example.
   384  		"components-can-add-the-same-base-if-the-first-renames-resources": {
   385  			input: []FileGen{writeTestBase,
   386  				deployment("proxy", "comp-a/proxy.yaml"),
   387  				writeC("comp-a", `
   388  resources:
   389  - ../base
   390  
   391  nameSuffix: "-a"
   392  `),
   393  				writeC("comp-b", `
   394  resources:
   395  - ../base
   396  
   397  nameSuffix: "-b"
   398  `),
   399  				writeK("prod", `
   400  components:
   401  - ../comp-a
   402  - ../comp-b`),
   403  			},
   404  			runPath: "prod",
   405  			expectedOutput: `
   406  apiVersion: v1
   407  kind: Deployment
   408  metadata:
   409    name: storefront-a-b
   410  spec:
   411    replicas: 1
   412  ---
   413  apiVersion: v1
   414  data:
   415    otherValue: green
   416    testValue: purple
   417  kind: ConfigMap
   418  metadata:
   419    name: my-configmap-a-b-9cd648hm8f
   420  ---
   421  apiVersion: v1
   422  kind: Deployment
   423  metadata:
   424    name: storefront-b
   425  spec:
   426    replicas: 1
   427  ---
   428  apiVersion: v1
   429  data:
   430    otherValue: green
   431    testValue: purple
   432  kind: ConfigMap
   433  metadata:
   434    name: my-configmap-b-9cd648hm8f
   435  `,
   436  		},
   437  
   438  		"multiple-bases-can-add-the-same-component-if-it-doesn-not-define-named-entities": {
   439  			input: []FileGen{
   440  				writeC("comp", `
   441  namespace: prod
   442  `),
   443  				writeK("base-a", `
   444  resources:
   445  - proxy.yaml
   446  
   447  components:
   448  - ../comp
   449  `),
   450  				deployment("proxy-a", "base-a/proxy.yaml"),
   451  				writeK("base-b", `
   452  resources:
   453  - proxy.yaml
   454  
   455  components:
   456  - ../comp
   457  `),
   458  				deployment("proxy-b", "base-b/proxy.yaml"),
   459  				writeK("prod", `
   460  resources:
   461  - proxy.yaml
   462  - ../base-a
   463  - ../base-b
   464  `),
   465  				deployment("proxy-prod", "prod/proxy.yaml"),
   466  			},
   467  			runPath: "prod",
   468  			// Note that the namepsace has not been applied to proxy-prod because it was not in scope when the
   469  			// component was applied
   470  			expectedOutput: `
   471  apiVersion: v1
   472  kind: Deployment
   473  metadata:
   474    name: proxy-prod
   475  spec:
   476    type: Logical
   477  ---
   478  apiVersion: v1
   479  kind: Deployment
   480  metadata:
   481    name: proxy-a
   482    namespace: prod
   483  spec:
   484    type: Logical
   485  ---
   486  apiVersion: v1
   487  kind: Deployment
   488  metadata:
   489    name: proxy-b
   490    namespace: prod
   491  spec:
   492    type: Logical
   493  `,
   494  		},
   495  	}
   496  
   497  	for tn, tc := range testCases {
   498  		t.Run(tn, func(t *testing.T) {
   499  			th := kusttest_test.MakeHarness(t)
   500  			for _, f := range tc.input {
   501  				f(th)
   502  			}
   503  			m := th.Run(tc.runPath, th.MakeDefaultOptions())
   504  			th.AssertActualEqualsExpected(m, tc.expectedOutput)
   505  		})
   506  	}
   507  }
   508  
   509  func TestComponentErrors(t *testing.T) {
   510  	testCases := map[string]struct {
   511  		input         []FileGen
   512  		runPath       string
   513  		expectedError string
   514  	}{
   515  		"components-cannot-be-added-to-resources": {
   516  			input: []FileGen{writeTestBase, writeTestComponent,
   517  				writeK("compinres", `
   518  resources:
   519  - ../base
   520  - ../comp
   521  `),
   522  			},
   523  			runPath:       "compinres",
   524  			expectedError: "expected kind != 'Component' for path '/comp'",
   525  		},
   526  		"kustomizations-cannot-be-added-to-components": {
   527  			input: []FileGen{writeTestBase, writeTestComponent,
   528  				writeK("kustincomponents", `
   529  components:
   530  - ../base
   531  - ../comp
   532  `),
   533  			},
   534  			runPath: "kustincomponents",
   535  			expectedError: "accumulating components: accumulateDirectory: \"expected kind 'Component' for path " +
   536  				"'/base' but got 'Kustomization'",
   537  		},
   538  		"files-cannot-be-added-to-components-list": {
   539  			input: []FileGen{writeTestBase,
   540  				writeF("filesincomponents/stub.yaml", `
   541  apiVersion: v1
   542  kind: Deployment
   543  metadata:
   544    name: stub
   545  spec:
   546    replicas: 1
   547  `),
   548  				writeK("filesincomponents", `
   549  components:
   550  - stub.yaml
   551  - ../comp
   552  `),
   553  			},
   554  			runPath:       "filesincomponents",
   555  			expectedError: fmt.Sprintf("%s: '%s'", loader.ErrRtNotDir.Error(), "/filesincomponents/stub.yaml"),
   556  		},
   557  		"invalid-component-api-version": {
   558  			input: []FileGen{writeTestBase, writeOverlayProd,
   559  				writeF("comp/"+konfig.DefaultKustomizationFileName(), `
   560  apiVersion: kustomize.config.k8s.io/v1beta1
   561  kind: Component
   562  configMapGenerator:
   563  - name: my-configmap
   564    behavior: merge
   565    literals:
   566    - otherValue=orange
   567  `),
   568  			},
   569  			runPath:       "prod",
   570  			expectedError: "apiVersion for Component should be kustomize.config.k8s.io/v1alpha1",
   571  		},
   572  		"components-cannot-add-the-same-resource": {
   573  			input: []FileGen{writeTestBase,
   574  				writeC("comp-a", `
   575  resources:
   576  - proxy.yaml
   577  `),
   578  				deployment("proxy", "comp-a/proxy.yaml"),
   579  				writeC("comp-b", `
   580  resources:
   581  - proxy.yaml
   582  `),
   583  				deployment("proxy", "comp-b/proxy.yaml"),
   584  				writeK("prod", `
   585  resources:
   586  - ../base
   587  
   588  components:
   589  - ../comp-a
   590  - ../comp-b`),
   591  			},
   592  			runPath:       "prod",
   593  			expectedError: "may not add resource with an already registered id: Deployment.v1.[noGrp]/proxy.[noNs]",
   594  		},
   595  		"components-cannot-add-the-same-base": {
   596  			input: []FileGen{writeTestBase,
   597  				deployment("proxy", "comp-a/proxy.yaml"),
   598  				writeC("comp-a", `
   599  resources:
   600  - ../base
   601  `),
   602  				writeC("comp-b", `
   603  resources:
   604  - ../base
   605  `),
   606  				writeK("prod", `
   607  components:
   608  - ../comp-a
   609  - ../comp-b`),
   610  			},
   611  			runPath:       "prod",
   612  			expectedError: "may not add resource with an already registered id: Deployment.v1.[noGrp]/storefront.[noNs]",
   613  		},
   614  		"components-cannot-add-bases-containing-the-same-resource": {
   615  			input: []FileGen{writeTestBase,
   616  				writeC("comp-a", `
   617  resources:
   618  - ../base-a
   619  `),
   620  				writeK("base-a", `
   621  resources:
   622  - proxy.yaml
   623  `),
   624  				deployment("proxy", "base-a/proxy.yaml"),
   625  				writeC("comp-b", `
   626  resources:
   627  - ../base-b
   628  `),
   629  				writeK("base-b", `
   630  resources:
   631  - proxy.yaml
   632  `),
   633  				deployment("proxy", "base-b/proxy.yaml"),
   634  				writeK("prod", `
   635  resources:
   636  - ../base
   637  
   638  components:
   639  - ../comp-a
   640  - ../comp-b`),
   641  			},
   642  			runPath:       "prod",
   643  			expectedError: "may not add resource with an already registered id: Deployment.v1.[noGrp]/proxy.[noNs]",
   644  		},
   645  	}
   646  
   647  	for tn, tc := range testCases {
   648  		t.Run(tn, func(t *testing.T) {
   649  			th := kusttest_test.MakeHarness(t)
   650  			for _, f := range tc.input {
   651  				f(th)
   652  			}
   653  			err := th.RunWithErr(tc.runPath, th.MakeDefaultOptions())
   654  			if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
   655  				t.Fatalf("unexpected error: %s", err)
   656  			}
   657  		})
   658  	}
   659  }
   660  
   661  func TestOrderOfAccumulatedComponent(t *testing.T) {
   662  	tests := map[string]struct {
   663  		input          []FileGen
   664  		expectedOutput string
   665  	}{
   666  		"components_using_a_generated_resource_by_configMapGenerator": {
   667  			input: []FileGen{
   668  				writeK("", `
   669  resources:
   670  - resource.yaml
   671  components:
   672  - components
   673  configMapGenerator:
   674  - name: generated-resource
   675    literals:
   676    - key=value
   677    options:
   678      disableNameSuffixHash: true`),
   679  				writeF("resource.yaml", `
   680  apiVersion: v1
   681  kind: ConfigMap
   682  metadata:
   683    name: config-from-resources
   684  data:
   685    foo: bar`),
   686  				writeC("/components", `
   687  apiVersion: kustomize.config.k8s.io/v1alpha1
   688  kind: Component
   689  nameSuffix: -suffix
   690  `),
   691  			},
   692  			expectedOutput: `
   693  apiVersion: v1
   694  data:
   695    foo: bar
   696  kind: ConfigMap
   697  metadata:
   698    name: config-from-resources-suffix
   699  ---
   700  apiVersion: v1
   701  data:
   702    key: value
   703  kind: ConfigMap
   704  metadata:
   705    name: generated-resource-suffix
   706  `},
   707  		"components_are_applied_before_replacements_transformer_applied": {
   708  			input: []FileGen{
   709  				writeK("", `
   710  resources:
   711  - resource.yaml
   712  components:
   713  - components
   714  replacements:
   715  - source:
   716      kind: Pod
   717      name: myapp-pod
   718      fieldPath: metadata.namespace
   719    targets:
   720    - select:
   721        kind: Pod
   722        name: myapp-pod
   723      fieldPaths:
   724      - spec.containers.[name=myapp-container].env.[name=CURRENT_POD_NAMESPACE].value`),
   725  				writeF("resource.yaml", `
   726  apiVersion: v1
   727  kind: Pod
   728  metadata:
   729    name: myapp-pod
   730    namespace: dev-assa
   731  spec:
   732    containers:
   733    - image: busybox
   734      name: myapp-container
   735      env:
   736      - name: CURRENT_POD_NAMESPACE
   737        value: "$(PLACEHOLDER)"`),
   738  				writeC("/components", `
   739  apiVersion: kustomize.config.k8s.io/v1alpha1
   740  kind: Component
   741  namespace: kustomize-namespace`),
   742  			},
   743  			expectedOutput: `
   744  apiVersion: v1
   745  kind: Pod
   746  metadata:
   747    name: myapp-pod
   748    namespace: kustomize-namespace
   749  spec:
   750    containers:
   751    - env:
   752      - name: CURRENT_POD_NAMESPACE
   753        value: kustomize-namespace
   754      image: busybox
   755      name: myapp-container
   756  `},
   757  	}
   758  
   759  	for testName, test := range tests {
   760  		t.Run(testName, func(t *testing.T) {
   761  			th := kusttest_test.MakeHarness(t)
   762  			for _, f := range test.input {
   763  				f(th)
   764  			}
   765  			m := th.Run(".", th.MakeDefaultOptions())
   766  			th.AssertActualEqualsExpected(m, test.expectedOutput)
   767  		})
   768  	}
   769  }
   770  

View as plain text