...

Source file src/sigs.k8s.io/kustomize/api/resource/factory_test.go

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package resource_test
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"sigs.k8s.io/kustomize/api/internal/loader"
    14  	. "sigs.k8s.io/kustomize/api/resource"
    15  	"sigs.k8s.io/kustomize/api/types"
    16  	"sigs.k8s.io/kustomize/kyaml/filesys"
    17  	"sigs.k8s.io/kustomize/kyaml/kio"
    18  )
    19  
    20  func TestRNodesFromBytes(t *testing.T) {
    21  	type testCase struct {
    22  		input    string
    23  		expected []string
    24  	}
    25  	testCases := map[string]testCase{
    26  		"empty1": {
    27  			input:    "",
    28  			expected: []string{},
    29  		},
    30  		"empty2": {
    31  			input: `
    32  ---
    33  ---
    34  `,
    35  			expected: []string{},
    36  		},
    37  		"deployment1": {
    38  			input: `
    39  apiVersion: apps/v1
    40  kind: Deployment
    41  metadata:
    42    name: pooh
    43  ---
    44  `,
    45  			expected: []string{
    46  				`apiVersion: apps/v1
    47  kind: Deployment
    48  metadata:
    49    name: pooh
    50  `,
    51  			},
    52  		},
    53  		"deployment2": {
    54  			input: `
    55  apiVersion: apps/v1
    56  kind: Deployment
    57  metadata:
    58    annotations:
    59      baseAnno: This is a base annotation
    60    labels:
    61      app: mungebot
    62      foo: bar
    63    name: baseprefix-mungebot
    64  spec:
    65    replicas: 1
    66    selector:
    67      matchLabels:
    68        foo: bar
    69    template:
    70      metadata:
    71        annotations:
    72          baseAnno: This is a base annotation
    73        labels:
    74          app: mungebot
    75          foo: bar
    76      spec:
    77        containers:
    78        - env:
    79          - name: foo
    80            value: bar
    81          image: nginx
    82          name: nginx
    83          ports:
    84          - containerPort: 80
    85  ---
    86  apiVersion: v1
    87  kind: Service
    88  metadata:
    89    annotations:
    90      baseAnno: This is a base annotation
    91    labels:
    92      app: mungebot
    93      foo: bar
    94    name: baseprefix-mungebot-service
    95  spec:
    96    ports:
    97    - port: 7002
    98    selector:
    99      app: mungebot
   100      foo: bar
   101  `,
   102  			expected: []string{
   103  				`apiVersion: apps/v1
   104  kind: Deployment
   105  metadata:
   106    annotations:
   107      baseAnno: This is a base annotation
   108    labels:
   109      app: mungebot
   110      foo: bar
   111    name: baseprefix-mungebot
   112  spec:
   113    replicas: 1
   114    selector:
   115      matchLabels:
   116        foo: bar
   117    template:
   118      metadata:
   119        annotations:
   120          baseAnno: This is a base annotation
   121        labels:
   122          app: mungebot
   123          foo: bar
   124      spec:
   125        containers:
   126        - env:
   127          - name: foo
   128            value: bar
   129          image: nginx
   130          name: nginx
   131          ports:
   132          - containerPort: 80
   133  `,
   134  				`apiVersion: v1
   135  kind: Service
   136  metadata:
   137    annotations:
   138      baseAnno: This is a base annotation
   139    labels:
   140      app: mungebot
   141      foo: bar
   142    name: baseprefix-mungebot-service
   143  spec:
   144    ports:
   145    - port: 7002
   146    selector:
   147      app: mungebot
   148      foo: bar
   149  `,
   150  			},
   151  		},
   152  	}
   153  
   154  	for name := range testCases {
   155  		tc := testCases[name]
   156  		t.Run(name, func(t *testing.T) {
   157  			result, err := factory.RNodesFromBytes([]byte(tc.input))
   158  			if err != nil {
   159  				t.Fatalf("%v: fails with err: %v", name, err)
   160  			}
   161  			if len(result) != len(tc.expected) {
   162  				for i := range result {
   163  					str, err := result[i].String()
   164  					if err != nil {
   165  						t.Fatalf("%v: result to YAML fails with err: %v", name, err)
   166  					}
   167  					t.Logf("--- %d:\n%s", i, str)
   168  				}
   169  				t.Fatalf(
   170  					"%v: actual len %d != expected len %d",
   171  					name, len(result), len(tc.expected))
   172  			}
   173  			for i := range tc.expected {
   174  				str, err := result[i].String()
   175  				if err != nil {
   176  					t.Fatalf("%v: result to YAML fails with err: %v", name, err)
   177  				}
   178  				if str != tc.expected[i] {
   179  					t.Fatalf(
   180  						"%v: string mismatch in item %d\n"+
   181  							"actual:\n-----\n%s\n-----\n"+
   182  							"expected:\n-----\n%s\n-----\n",
   183  						name, i, str, tc.expected[i])
   184  				}
   185  			}
   186  		})
   187  	}
   188  }
   189  
   190  func TestSliceFromPatches(t *testing.T) {
   191  	patchGood1 := types.PatchStrategicMerge("patch1.yaml")
   192  	patch1 := `
   193  apiVersion: apps/v1
   194  kind: Deployment
   195  metadata:
   196    name: pooh
   197  `
   198  	patchGood2 := types.PatchStrategicMerge("patch2.yaml")
   199  	patch2 := `
   200  apiVersion: v1
   201  kind: ConfigMap
   202  metadata:
   203    name: winnie
   204    namespace: hundred-acre-wood
   205  ---
   206  # some comment
   207  ---
   208  ---
   209  `
   210  	patchBad := types.PatchStrategicMerge("patch3.yaml")
   211  	patch3 := `
   212  WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOT: woot
   213  `
   214  	patchList := types.PatchStrategicMerge("patch4.yaml")
   215  	patch4 := `
   216  apiVersion: v1
   217  kind: List
   218  items:
   219  - apiVersion: apps/v1
   220    kind: Deployment
   221    metadata:
   222      name: pooh
   223  - apiVersion: v1
   224    kind: ConfigMap
   225    metadata:
   226      name: winnie
   227      namespace: hundred-acre-wood
   228  `
   229  	patchList2 := types.PatchStrategicMerge("patch5.yaml")
   230  	patch5 := `
   231  apiVersion: v1
   232  kind: DeploymentList
   233  items:
   234  - apiVersion: apps/v1
   235    kind: Deployment
   236    metadata:
   237      name: deployment-a
   238    spec: &hostAliases
   239      template:
   240        spec:
   241          hostAliases:
   242          - hostnames:
   243            - a.example.com
   244            ip: 8.8.8.8
   245  - apiVersion: apps/v1
   246    kind: Deployment
   247    metadata:
   248      name: deployment-b
   249    spec:
   250      <<: *hostAliases
   251  `
   252  	patchList3 := types.PatchStrategicMerge("patch6.yaml")
   253  	patch6 := `
   254  apiVersion: v1
   255  kind: List
   256  items:
   257  `
   258  	patchList4 := types.PatchStrategicMerge("patch7.yaml")
   259  	patch7 := `
   260  apiVersion: v1
   261  kind: List
   262  `
   263  	testDeploymentSpec := map[string]interface{}{
   264  		"template": map[string]interface{}{
   265  			"spec": map[string]interface{}{
   266  				"hostAliases": []interface{}{
   267  					map[string]interface{}{
   268  						"hostnames": []interface{}{
   269  							"a.example.com",
   270  						},
   271  						"ip": "8.8.8.8",
   272  					},
   273  				},
   274  			},
   275  		},
   276  	}
   277  	testDeploymentA := factory.FromMap(
   278  		map[string]interface{}{
   279  			"apiVersion": "apps/v1",
   280  			"kind":       "Deployment",
   281  			"metadata": map[string]interface{}{
   282  				"name": "deployment-a",
   283  			},
   284  			"spec": testDeploymentSpec,
   285  		})
   286  	testDeploymentB := factory.FromMap(
   287  		map[string]interface{}{
   288  			"apiVersion": "apps/v1",
   289  			"kind":       "Deployment",
   290  			"metadata": map[string]interface{}{
   291  				"name": "deployment-b",
   292  			},
   293  			"spec": testDeploymentSpec,
   294  		})
   295  
   296  	fSys := filesys.MakeFsInMemory()
   297  	fSys.WriteFile(string(patchGood1), []byte(patch1))
   298  	fSys.WriteFile(string(patchGood2), []byte(patch2))
   299  	fSys.WriteFile(string(patchBad), []byte(patch3))
   300  	fSys.WriteFile(string(patchList), []byte(patch4))
   301  	fSys.WriteFile(string(patchList2), []byte(patch5))
   302  	fSys.WriteFile(string(patchList3), []byte(patch6))
   303  	fSys.WriteFile(string(patchList4), []byte(patch7))
   304  
   305  	ldr, err := loader.NewLoader(
   306  		loader.RestrictionRootOnly, filesys.Separator, fSys)
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  
   311  	tests := map[string]struct {
   312  		input       []types.PatchStrategicMerge
   313  		expectedOut []*Resource
   314  		expectedErr bool
   315  	}{
   316  		"happy": {
   317  			input:       []types.PatchStrategicMerge{patchGood1, patchGood2},
   318  			expectedOut: []*Resource{testDeployment, testConfigMap},
   319  			expectedErr: false,
   320  		},
   321  		"badFileName": {
   322  			input:       []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
   323  			expectedOut: []*Resource{},
   324  			expectedErr: true,
   325  		},
   326  		"badData": {
   327  			input:       []types.PatchStrategicMerge{patchGood1, patchBad},
   328  			expectedOut: []*Resource{},
   329  			expectedErr: true,
   330  		},
   331  		"listOfPatches": {
   332  			input:       []types.PatchStrategicMerge{patchList},
   333  			expectedOut: []*Resource{testDeployment, testConfigMap},
   334  			expectedErr: false,
   335  		},
   336  		"listWithAnchorReference": {
   337  			input:       []types.PatchStrategicMerge{patchList2},
   338  			expectedOut: []*Resource{testDeploymentA, testDeploymentB},
   339  			expectedErr: false,
   340  		},
   341  		"listWithNoEntries": {
   342  			input:       []types.PatchStrategicMerge{patchList3},
   343  			expectedOut: []*Resource{},
   344  			expectedErr: false,
   345  		},
   346  		"listWithNoItems": {
   347  			input:       []types.PatchStrategicMerge{patchList4},
   348  			expectedOut: []*Resource{},
   349  			expectedErr: false,
   350  		},
   351  	}
   352  	for n, test := range tests {
   353  		t.Run(n, func(t *testing.T) {
   354  			rs, err := factory.SliceFromPatches(ldr, test.input)
   355  			if err != nil {
   356  				assert.True(t, test.expectedErr,
   357  					fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
   358  				return
   359  			}
   360  			assert.False(t, test.expectedErr, "expected no error in "+n)
   361  			assert.Equal(t, len(test.expectedOut), len(rs))
   362  			for i := range rs {
   363  				expYaml, err := test.expectedOut[i].AsYAML()
   364  				require.NoError(t, err)
   365  				actYaml, err := rs[i].AsYAML()
   366  				require.NoError(t, err)
   367  				assert.Equal(t, expYaml, actYaml)
   368  			}
   369  		})
   370  	}
   371  }
   372  
   373  func TestHash(t *testing.T) {
   374  	input := `
   375  apiVersion: v1
   376  kind: ConfigMap
   377  metadata:
   378    name: foo
   379  data:
   380    one: ""
   381  binaryData:
   382    two: ""
   383  `
   384  	expect := "698h7c7t9m"
   385  	k, err := factory.SliceFromBytes([]byte(input))
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  	result, err := k[0].Hash(factory.Hasher())
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	if result != expect {
   394  		t.Fatalf("expect %s but got %s", expect, result)
   395  	}
   396  }
   397  
   398  func TestMoreRNodesFromBytes(t *testing.T) {
   399  	type expected struct {
   400  		out   []string
   401  		isErr bool
   402  	}
   403  	testCases := map[string]struct {
   404  		input []byte
   405  		exp   expected
   406  	}{
   407  		"garbage": {
   408  			input: []byte("garbageIn: garbageOut"),
   409  			exp: expected{
   410  				isErr: true,
   411  			},
   412  		},
   413  		"noBytes": {
   414  			input: []byte{},
   415  			exp:   expected{},
   416  		},
   417  		"goodJson": {
   418  			input: []byte(`
   419  {"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
   420  `),
   421  			exp: expected{
   422  				out: []string{
   423  					`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
   424  				},
   425  			},
   426  		},
   427  		"goodYaml1": {
   428  			input: []byte(`
   429  apiVersion: v1
   430  kind: ConfigMap
   431  metadata:
   432    name: winnie
   433  `),
   434  			exp: expected{
   435  				out: []string{`
   436  apiVersion: v1
   437  kind: ConfigMap
   438  metadata:
   439    name: winnie
   440  `},
   441  			},
   442  		},
   443  		"goodYaml2": {
   444  			input: []byte(`
   445  apiVersion: v1
   446  kind: ConfigMap
   447  metadata:
   448    name: winnie
   449  ---
   450  apiVersion: v1
   451  kind: ConfigMap
   452  metadata:
   453    name: winnie
   454  `),
   455  			exp: expected{
   456  				out: []string{`
   457  apiVersion: v1
   458  kind: ConfigMap
   459  metadata:
   460    name: winnie
   461  `, `
   462  apiVersion: v1
   463  kind: ConfigMap
   464  metadata:
   465    name: winnie
   466  `},
   467  			},
   468  		},
   469  		"garbageInOneOfTwoObjects": {
   470  			input: []byte(`
   471  apiVersion: v1
   472  kind: ConfigMap
   473  metadata:
   474    name: winnie
   475  ---
   476  WOOOOOOOOOOOOOOOOOOOOOOOOT:  woot
   477  `),
   478  			exp: expected{
   479  				isErr: true,
   480  			},
   481  		},
   482  		"emptyObjects": {
   483  			input: []byte(`
   484  ---
   485  #a comment
   486  
   487  ---
   488  
   489  `),
   490  			exp: expected{
   491  				out: []string{},
   492  			},
   493  		},
   494  		"Missing .metadata.name in object": {
   495  			input: []byte(`
   496  apiVersion: v1
   497  kind: Namespace
   498  metadata:
   499    annotations:
   500      foo: bar
   501  `),
   502  			exp: expected{
   503  				isErr: true,
   504  			},
   505  		},
   506  		"nil value in list": {
   507  			input: []byte(`
   508  apiVersion: builtin
   509  kind: ConfigMapGenerator
   510  metadata:
   511    name: kube100-site
   512  	labels:
   513  	  app: web
   514  testList:
   515  - testA
   516  -
   517  `),
   518  			exp: expected{
   519  				isErr: true,
   520  			},
   521  		},
   522  		"List": {
   523  			input: []byte(`
   524  apiVersion: v1
   525  kind: List
   526  items:
   527  - apiVersion: v1
   528    kind: ConfigMap
   529    metadata:
   530      name: winnie
   531  - apiVersion: v1
   532    kind: ConfigMap
   533    metadata:
   534      name: winnie
   535  `),
   536  			exp: expected{
   537  				out: []string{`
   538  apiVersion: v1
   539  kind: ConfigMap
   540  metadata:
   541    name: winnie
   542  `, `
   543  apiVersion: v1
   544  kind: ConfigMap
   545  metadata:
   546    name: winnie
   547  `,
   548  				},
   549  			},
   550  		},
   551  		"ConfigMapList": {
   552  			input: []byte(`
   553  apiVersion: v1
   554  kind: ConfigMapList
   555  items:
   556  - apiVersion: v1
   557    kind: ConfigMap
   558    metadata:
   559      name: winnie
   560  - apiVersion: v1
   561    kind: ConfigMap
   562    metadata:
   563      name: winnie
   564  `),
   565  			exp: expected{
   566  				out: []string{
   567  					`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
   568  					`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
   569  				},
   570  			},
   571  		},
   572  		"listWithAnchors": {
   573  			input: []byte(`
   574  apiVersion: v1
   575  kind: DeploymentList
   576  items:
   577  - apiVersion: apps/v1
   578    kind: Deployment
   579    metadata:
   580      name: deployment-a
   581    spec: &foo
   582      template:
   583        spec:
   584          hostAliases:
   585          - hostnames:
   586            - a.example.com
   587            ip: 8.8.8.8
   588  - apiVersion: apps/v1
   589    kind: Deployment
   590    metadata:
   591      name: deployment-b
   592    spec:
   593      *foo
   594  `),
   595  			exp: expected{
   596  				out: []string{
   597  					`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-a"}, ` +
   598  						`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`,
   599  					`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-b"}, ` +
   600  						`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`},
   601  			},
   602  		},
   603  		"simpleAnchor": {
   604  			input: []byte(`
   605  apiVersion: v1
   606  kind: ConfigMap
   607  metadata:
   608    name: wildcard
   609  data:
   610    color: &color-used blue
   611    feeling: *color-used
   612  `),
   613  			exp: expected{
   614  				out: []string{`
   615  apiVersion: v1
   616  kind: ConfigMap
   617  metadata:
   618    name: wildcard
   619  data:
   620    color: blue
   621    feeling: blue
   622  `},
   623  			},
   624  		},
   625  	}
   626  	for n := range testCases {
   627  		tc := testCases[n]
   628  		t.Run(n, func(t *testing.T) {
   629  			rs, err := factory.RNodesFromBytes(tc.input)
   630  			if err != nil {
   631  				assert.True(t, tc.exp.isErr)
   632  				return
   633  			}
   634  			assert.False(t, tc.exp.isErr)
   635  			assert.Equal(t, len(tc.exp.out), len(rs))
   636  			for i := range rs {
   637  				actual, err := rs[i].String()
   638  				require.NoError(t, err)
   639  				assert.Equal(
   640  					t, strings.TrimSpace(tc.exp.out[i]), strings.TrimSpace(actual))
   641  			}
   642  		})
   643  	}
   644  }
   645  
   646  func TestDropLocalNodes(t *testing.T) {
   647  	testCases := map[string]struct {
   648  		input    []byte
   649  		expected []byte
   650  	}{
   651  		"localConfigUnset": {
   652  			input: []byte(`apiVersion: v1
   653  kind: ConfigMap
   654  metadata:
   655    name: winnie
   656  `),
   657  			expected: []byte(`apiVersion: v1
   658  kind: ConfigMap
   659  metadata:
   660    name: winnie
   661  `),
   662  		},
   663  		"localConfigSet": {
   664  			input: []byte(`apiVersion: v1
   665  kind: ConfigMap
   666  metadata:
   667    name: winnie-skip
   668    annotations:
   669       # this annotation causes the Resource to be ignored by kustomize
   670       config.kubernetes.io/local-config: ""
   671  `),
   672  			expected: nil,
   673  		},
   674  		"localConfigSetToTrue": {
   675  			input: []byte(`apiVersion: v1
   676  kind: ConfigMap
   677  metadata:
   678    name: winnie-skip
   679    annotations:
   680  	 config.kubernetes.io/local-config: "true"
   681  		`),
   682  			expected: nil,
   683  		},
   684  		"localConfigSetToFalse": {
   685  			input: []byte(`apiVersion: v1
   686  kind: ConfigMap
   687  metadata:
   688    name: winnie
   689    annotations:
   690      config.kubernetes.io/local-config: "false"
   691  `),
   692  			expected: []byte(`apiVersion: v1
   693  kind: ConfigMap
   694  metadata:
   695    annotations:
   696      config.kubernetes.io/local-config: "false"
   697    name: winnie
   698  `),
   699  		},
   700  		"localConfigMultiInput": {
   701  			input: []byte(`apiVersion: v1
   702  kind: ConfigMap
   703  metadata:
   704    name: winnie
   705  ---
   706  apiVersion: v1
   707  kind: ConfigMap
   708  metadata:
   709    name: winnie-skip
   710    annotations:
   711      config.kubernetes.io/local-config: "true"
   712  `),
   713  			expected: []byte(`apiVersion: v1
   714  kind: ConfigMap
   715  metadata:
   716    name: winnie
   717  `),
   718  		},
   719  	}
   720  	for n := range testCases {
   721  		tc := testCases[n]
   722  		t.Run(n, func(t *testing.T) {
   723  			nin, _ := kio.FromBytes(tc.input)
   724  			res, err := factory.DropLocalNodes(nin)
   725  			require.NoError(t, err)
   726  			if tc.expected == nil {
   727  				assert.Equal(t, 0, len(res))
   728  			} else {
   729  				actual, _ := res[0].AsYAML()
   730  				assert.Equal(t, tc.expected, actual)
   731  			}
   732  		})
   733  	}
   734  }
   735  

View as plain text