...

Source file src/sigs.k8s.io/kustomize/kyaml/kio/byteio_reader_test.go

Documentation: sigs.k8s.io/kustomize/kyaml/kio

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package kio_test
     5  
     6  import (
     7  	"bytes"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	. "sigs.k8s.io/kustomize/kyaml/kio"
    14  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    15  	"sigs.k8s.io/kustomize/kyaml/yaml"
    16  )
    17  
    18  func TestByteReader(t *testing.T) {
    19  	type testCase struct {
    20  		name                   string
    21  		input                  string
    22  		err                    string
    23  		expectedItems          []string
    24  		expectedFunctionConfig string
    25  		expectedResults        string
    26  		wrappingAPIVersion     string
    27  		wrappingAPIKind        string
    28  		instance               ByteReader
    29  	}
    30  
    31  	testCases := []testCase{
    32  		//
    33  		//
    34  		//
    35  		{
    36  			name: "wrapped_resource_list",
    37  			input: `apiVersion: config.kubernetes.io/v1
    38  kind: ResourceList
    39  items:
    40  -  kind: Deployment
    41     spec:
    42       replicas: 1
    43  - kind: Service
    44    spec:
    45      selectors:
    46        foo: bar
    47  `,
    48  			expectedItems: []string{
    49  				`kind: Deployment
    50  spec:
    51    replicas: 1
    52  `,
    53  				`kind: Service
    54  spec:
    55    selectors:
    56      foo: bar
    57  `,
    58  			},
    59  			wrappingAPIVersion: ResourceListAPIVersion,
    60  			wrappingAPIKind:    ResourceListKind,
    61  		},
    62  
    63  		//
    64  		//
    65  		//
    66  		{
    67  			name: "wrapped_resource_list_function_config",
    68  			input: `apiVersion: config.kubernetes.io/v1
    69  kind: ResourceList
    70  functionConfig:
    71    foo: bar
    72    elems:
    73    - a
    74    - b
    75    - c
    76  items:
    77  -  kind: Deployment
    78     spec:
    79       replicas: 1
    80  - kind: Service
    81    spec:
    82      selectors:
    83        foo: bar
    84  `,
    85  			expectedItems: []string{
    86  				`kind: Deployment
    87  spec:
    88    replicas: 1
    89  `,
    90  				`kind: Service
    91  spec:
    92    selectors:
    93      foo: bar
    94  `,
    95  			},
    96  			expectedFunctionConfig: `foo: bar
    97  elems:
    98  - a
    99  - b
   100  - c`,
   101  			wrappingAPIVersion: ResourceListAPIVersion,
   102  			wrappingAPIKind:    ResourceListKind,
   103  		},
   104  		//
   105  		//
   106  		//
   107  		{
   108  			name: "wrapped_resource_list_function_config_without_items",
   109  			input: `apiVersion: config.kubernetes.io/v1
   110  kind: ResourceList
   111  functionConfig:
   112    foo: bar
   113    elems:
   114    - a
   115    - b
   116    - c
   117  `,
   118  			expectedItems: []string{},
   119  			expectedFunctionConfig: `foo: bar
   120  elems:
   121  - a
   122  - b
   123  - c`,
   124  			wrappingAPIVersion: ResourceListAPIVersion,
   125  			wrappingAPIKind:    ResourceListKind,
   126  		},
   127  
   128  		//
   129  		//
   130  		//
   131  		{
   132  			name: "wrapped_list",
   133  			input: `
   134  apiVersion: v1
   135  kind: List
   136  items:
   137  - kind: Deployment
   138    spec:
   139      replicas: 1
   140  - kind: Service
   141    spec:
   142      selectors:
   143        foo: bar
   144  `,
   145  			expectedItems: []string{
   146  				`
   147  kind: Deployment
   148  spec:
   149    replicas: 1
   150  `,
   151  				`
   152  kind: Service
   153  spec:
   154    selectors:
   155      foo: bar
   156  `,
   157  			},
   158  			wrappingAPIKind:    "List",
   159  			wrappingAPIVersion: "v1",
   160  		},
   161  
   162  		//
   163  		//
   164  		//
   165  		{
   166  			name: "unwrapped_items",
   167  			input: `
   168  ---
   169  a: b # first resource
   170  c: d
   171  ---
   172  # second resource
   173  e: f
   174  g:
   175  - h
   176  ---
   177  ---
   178  i: j
   179  `,
   180  			expectedItems: []string{
   181  				`a: b # first resource
   182  c: d
   183  metadata:
   184    annotations:
   185      config.kubernetes.io/index: '0'
   186      internal.config.kubernetes.io/index: '0'
   187  `,
   188  				`# second resource
   189  e: f
   190  g:
   191  - h
   192  metadata:
   193    annotations:
   194      config.kubernetes.io/index: '1'
   195      internal.config.kubernetes.io/index: '1'
   196  `,
   197  				`i: j
   198  metadata:
   199    annotations:
   200      config.kubernetes.io/index: '2'
   201      internal.config.kubernetes.io/index: '2'
   202  `,
   203  			},
   204  		},
   205  
   206  		//
   207  		//
   208  		//
   209  		{
   210  			name: "omit_annotations",
   211  			input: `
   212  ---
   213  a: b # first resource
   214  c: d
   215  ---
   216  # second resource
   217  e: f
   218  g:
   219  - h
   220  ---
   221  ---
   222  i: j
   223  `,
   224  			expectedItems: []string{
   225  				`
   226  a: b # first resource
   227  c: d
   228  `,
   229  				`
   230  # second resource
   231  e: f
   232  g:
   233  - h
   234  `,
   235  				`
   236  i: j
   237  `,
   238  			},
   239  			instance: ByteReader{OmitReaderAnnotations: true},
   240  		},
   241  
   242  		//
   243  		//
   244  		//
   245  		{
   246  			name: "no_omit_annotations",
   247  			input: `
   248  ---
   249  a: b # first resource
   250  c: d
   251  ---
   252  # second resource
   253  e: f
   254  g:
   255  - h
   256  ---
   257  ---
   258  i: j
   259  `,
   260  			expectedItems: []string{
   261  				`
   262  a: b # first resource
   263  c: d
   264  metadata:
   265    annotations:
   266      config.kubernetes.io/index: '0'
   267      internal.config.kubernetes.io/index: '0'
   268  `,
   269  				`
   270  # second resource
   271  e: f
   272  g:
   273  - h
   274  metadata:
   275    annotations:
   276      config.kubernetes.io/index: '1'
   277      internal.config.kubernetes.io/index: '1'
   278  `,
   279  				`
   280  i: j
   281  metadata:
   282    annotations:
   283      config.kubernetes.io/index: '2'
   284      internal.config.kubernetes.io/index: '2'
   285  `,
   286  			},
   287  			instance: ByteReader{},
   288  		},
   289  
   290  		//
   291  		//
   292  		//
   293  		{
   294  			name: "set_annotation",
   295  			input: `
   296  ---
   297  a: b # first resource
   298  c: d
   299  ---
   300  # second resource
   301  e: f
   302  g:
   303  - h
   304  ---
   305  ---
   306  i: j
   307  `,
   308  			expectedItems: []string{
   309  				`a: b # first resource
   310  c: d
   311  metadata:
   312    annotations:
   313      foo: 'bar'
   314  `,
   315  				`# second resource
   316  e: f
   317  g:
   318  - h
   319  metadata:
   320    annotations:
   321      foo: 'bar'
   322  `,
   323  				`i: j
   324  metadata:
   325    annotations:
   326      foo: 'bar'
   327  `,
   328  			},
   329  			instance: ByteReader{
   330  				OmitReaderAnnotations: true,
   331  				SetAnnotations:        map[string]string{"foo": "bar"}},
   332  		},
   333  
   334  		//
   335  		//
   336  		//
   337  		{
   338  			name:  "windows_line_ending",
   339  			input: "\r\n---\r\na: b # first resource\r\nc: d\r\n---\r\n# second resource\r\ne: f\r\ng:\r\n- h\r\n---\r\n\r\n---\r\n i: j",
   340  			expectedItems: []string{
   341  				`a: b # first resource
   342  c: d
   343  metadata:
   344    annotations:
   345      foo: 'bar'
   346  `,
   347  				`# second resource
   348  e: f
   349  g:
   350  - h
   351  metadata:
   352    annotations:
   353      foo: 'bar'
   354  `,
   355  				`i: j
   356  metadata:
   357    annotations:
   358      foo: 'bar'
   359  `,
   360  			},
   361  			instance: ByteReader{
   362  				OmitReaderAnnotations: true,
   363  				SetAnnotations:        map[string]string{"foo": "bar"}},
   364  		},
   365  
   366  		//
   367  		//
   368  		//
   369  		{
   370  			name: "json",
   371  			input: `
   372  {
   373    "a": "b",
   374    "c": [1, 2]
   375  }
   376  `,
   377  			expectedItems: []string{
   378  				`
   379  {"a": "b", "c": [1, 2], metadata: {annotations: {config.kubernetes.io/index: '0', internal.config.kubernetes.io/index: '0'}}}
   380  `,
   381  			},
   382  			instance: ByteReader{},
   383  		},
   384  
   385  		//
   386  		//
   387  		//
   388  		{
   389  			name: "white_space_after_document_separator_should_be_ignored",
   390  			input: `
   391  a: b
   392  ---         
   393  c: d
   394  `,
   395  			expectedItems: []string{
   396  				`
   397  a: b
   398  `,
   399  				`
   400  c: d
   401  `,
   402  			},
   403  			instance: ByteReader{OmitReaderAnnotations: true},
   404  		},
   405  
   406  		//
   407  		//
   408  		//
   409  		{
   410  			name: "comment_after_document_separator_should_be_ignored",
   411  			input: `
   412  a: b
   413  --- #foo
   414  c: d
   415  `,
   416  			expectedItems: []string{
   417  				`
   418  a: b
   419  `,
   420  				`
   421  c: d
   422  `,
   423  			},
   424  			instance: ByteReader{OmitReaderAnnotations: true},
   425  		},
   426  
   427  		//
   428  		//
   429  		//
   430  		{
   431  			name: "anything_after_document_separator_other_than_white_space_or_comment_is_an_error",
   432  			input: `
   433  a: b
   434  --- foo
   435  c: d
   436  `,
   437  			err:      "invalid document separator: --- foo",
   438  			instance: ByteReader{OmitReaderAnnotations: true},
   439  		},
   440  	}
   441  
   442  	for i := range testCases {
   443  		tc := testCases[i]
   444  		t.Run(tc.name, func(t *testing.T) {
   445  			r := tc.instance
   446  			r.Reader = bytes.NewBufferString(tc.input)
   447  			nodes, err := r.Read()
   448  			if tc.err != "" {
   449  				if !assert.EqualError(t, err, tc.err) {
   450  					t.FailNow()
   451  				}
   452  				return
   453  			}
   454  
   455  			if !assert.NoError(t, err) {
   456  				t.FailNow()
   457  			}
   458  
   459  			// verify the contents
   460  			if !assert.Len(t, nodes, len(tc.expectedItems)) {
   461  				t.FailNow()
   462  			}
   463  			for i := range nodes {
   464  				actual, err := nodes[i].String()
   465  				if !assert.NoError(t, err) {
   466  					t.FailNow()
   467  				}
   468  				if !assert.Equal(t,
   469  					strings.TrimSpace(tc.expectedItems[i]),
   470  					strings.TrimSpace(actual)) {
   471  					t.FailNow()
   472  				}
   473  			}
   474  
   475  			// verify the function config
   476  			if tc.expectedFunctionConfig != "" {
   477  				actual, err := r.FunctionConfig.String()
   478  				if !assert.NoError(t, err) {
   479  					t.FailNow()
   480  				}
   481  				if !assert.Equal(t,
   482  					strings.TrimSpace(tc.expectedFunctionConfig),
   483  					strings.TrimSpace(actual)) {
   484  					t.FailNow()
   485  				}
   486  			} else if !assert.Nil(t, r.FunctionConfig) {
   487  				t.FailNow()
   488  			}
   489  
   490  			if tc.expectedResults != "" {
   491  				actual, err := r.Results.String()
   492  				actual = strings.TrimSpace(actual)
   493  				if !assert.NoError(t, err) {
   494  					t.FailNow()
   495  				}
   496  
   497  				tc.expectedResults = strings.TrimSpace(tc.expectedResults)
   498  				if !assert.Equal(t, tc.expectedResults, actual) {
   499  					t.FailNow()
   500  				}
   501  			} else if !assert.Nil(t, r.Results) {
   502  				t.FailNow()
   503  			}
   504  
   505  			if !assert.Equal(t, tc.wrappingAPIKind, r.WrappingKind) {
   506  				t.FailNow()
   507  			}
   508  			if !assert.Equal(t, tc.wrappingAPIVersion, r.WrappingAPIVersion) {
   509  				t.FailNow()
   510  			}
   511  		})
   512  	}
   513  }
   514  
   515  func TestFromBytes(t *testing.T) {
   516  	type expected struct {
   517  		isErr bool
   518  		sOut  []string
   519  	}
   520  
   521  	testCases := map[string]struct {
   522  		input []byte
   523  		exp   expected
   524  	}{
   525  		"garbage": {
   526  			input: []byte("garbageIn: garbageOut"),
   527  			exp: expected{
   528  				sOut: []string{"garbageIn: garbageOut"},
   529  			},
   530  		},
   531  		"noBytes": {
   532  			input: []byte{},
   533  			exp: expected{
   534  				sOut: []string{},
   535  			},
   536  		},
   537  		"goodJson": {
   538  			input: []byte(`
   539  {"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
   540  `),
   541  			exp: expected{
   542  				sOut: []string{`
   543  {"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}
   544  `},
   545  			},
   546  		},
   547  		"goodYaml1": {
   548  			input: []byte(`
   549  apiVersion: v1
   550  kind: ConfigMap
   551  metadata:
   552    name: winnie
   553  `),
   554  			exp: expected{
   555  				sOut: []string{`
   556  apiVersion: v1
   557  kind: ConfigMap
   558  metadata:
   559    name: winnie
   560  `},
   561  			},
   562  		},
   563  		"goodYaml2": {
   564  			input: []byte(`
   565  apiVersion: v1
   566  kind: ConfigMap
   567  metadata:
   568    name: winnie
   569  ---
   570  apiVersion: v1
   571  kind: ConfigMap
   572  metadata:
   573    name: winnie
   574  `),
   575  			exp: expected{
   576  				sOut: []string{`
   577  apiVersion: v1
   578  kind: ConfigMap
   579  metadata:
   580    name: winnie
   581  `, `
   582  apiVersion: v1
   583  kind: ConfigMap
   584  metadata:
   585    name: winnie
   586  `},
   587  			},
   588  		},
   589  		"localConfigYaml": {
   590  			input: []byte(`
   591  apiVersion: v1
   592  kind: ConfigMap
   593  metadata:
   594    name: winnie-skip
   595    annotations:
   596      # this annotation causes the Resource to be ignored by kustomize
   597      config.kubernetes.io/local-config: ""
   598  ---
   599  apiVersion: v1
   600  kind: ConfigMap
   601  metadata:
   602    name: winnie
   603  `),
   604  			exp: expected{
   605  				sOut: []string{`
   606  apiVersion: v1
   607  kind: ConfigMap
   608  metadata:
   609    name: winnie-skip
   610    annotations:
   611      # this annotation causes the Resource to be ignored by kustomize
   612      config.kubernetes.io/local-config: ""
   613  `,
   614  					`
   615  apiVersion: v1
   616  kind: ConfigMap
   617  metadata:
   618    name: winnie
   619  `},
   620  			},
   621  		},
   622  		"garbageInOneOfTwoObjects": {
   623  			input: []byte(`
   624  apiVersion: v1
   625  kind: ConfigMap
   626  metadata:
   627    name: winnie
   628  ---
   629  WOOOOOOOOOOOOOOOOOOOOOOOOT:     woot
   630  `),
   631  			exp: expected{
   632  				sOut: []string{`
   633  apiVersion: v1
   634  kind: ConfigMap
   635  metadata:
   636    name: winnie
   637  `,
   638  					`
   639  WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
   640  `},
   641  			},
   642  		},
   643  		"emptyObjects": {
   644  			input: []byte(`
   645  ---
   646  #a comment
   647  
   648  ---
   649  
   650  `),
   651  			exp: expected{
   652  				sOut: []string{},
   653  			},
   654  		},
   655  		"Missing .metadata.name in object": {
   656  			input: []byte(`
   657  apiVersion: v1
   658  kind: Namespace
   659  metadata:
   660    annotations:
   661      foo: bar
   662  `),
   663  			exp: expected{
   664  				sOut: []string{`
   665  apiVersion: v1
   666  kind: Namespace
   667  metadata:
   668    annotations:
   669      foo: bar
   670  `},
   671  			},
   672  		},
   673  		"nil value in list": {
   674  			input: []byte(`
   675  apiVersion: builtin
   676  kind: ConfigMapGenerator
   677  metadata:
   678    name: kube100-site
   679  	labels:
   680  	  app: web
   681  testList:
   682  - testA
   683  -
   684  `),
   685  			exp: expected{
   686  				isErr: true,
   687  			},
   688  		},
   689  		"List": {
   690  			input: []byte(`
   691  apiVersion: v1
   692  kind: List
   693  items:
   694  - apiVersion: v1
   695    kind: ConfigMap
   696    metadata:
   697      name: winnie
   698  - apiVersion: v1
   699    kind: ConfigMap
   700    metadata:
   701      name: winnie
   702  `),
   703  			exp: expected{
   704  				sOut: []string{`
   705  apiVersion: v1
   706  kind: ConfigMap
   707  metadata:
   708    name: winnie
   709  `, `
   710  apiVersion: v1
   711  kind: ConfigMap
   712  metadata:
   713    name: winnie
   714  `},
   715  			},
   716  		},
   717  		"ConfigMapList": {
   718  			input: []byte(`
   719  apiVersion: v1
   720  kind: ConfigMapList
   721  items:
   722  - apiVersion: v1
   723    kind: ConfigMap
   724    metadata:
   725      name: winnie
   726  - apiVersion: v1
   727    kind: ConfigMap
   728    metadata:
   729      name: winnie
   730  `),
   731  			exp: expected{
   732  				sOut: []string{`
   733  apiVersion: v1
   734  kind: ConfigMapList
   735  items:
   736  - apiVersion: v1
   737    kind: ConfigMap
   738    metadata:
   739      name: winnie
   740  - apiVersion: v1
   741    kind: ConfigMap
   742    metadata:
   743      name: winnie
   744  `},
   745  			},
   746  		},
   747  		"mergeAnchors": {
   748  			input: []byte(`
   749  apiVersion: v1
   750  kind: DeploymentList
   751  items:
   752  - apiVersion: apps/v1
   753    kind: Deployment
   754    metadata:
   755      name: deployment-a
   756    spec: &hostAliases
   757      template:
   758        spec:
   759          hostAliases:
   760          - hostnames:
   761            - a.example.com
   762            ip: 8.8.8.8
   763  - apiVersion: apps/v1
   764    kind: Deployment
   765    metadata:
   766      name: deployment-b
   767    spec:
   768      <<: *hostAliases
   769  `),
   770  			exp: expected{
   771  				sOut: []string{`
   772  apiVersion: v1
   773  kind: DeploymentList
   774  items:
   775  - apiVersion: apps/v1
   776    kind: Deployment
   777    metadata:
   778      name: deployment-a
   779    spec:
   780      template:
   781        spec:
   782          hostAliases:
   783          - hostnames:
   784            - a.example.com
   785            ip: 8.8.8.8
   786  - apiVersion: apps/v1
   787    kind: Deployment
   788    metadata:
   789      name: deployment-b
   790    spec:
   791      template:
   792        spec:
   793          hostAliases:
   794          - hostnames:
   795            - a.example.com
   796            ip: 8.8.8.8
   797  `},
   798  			},
   799  		},
   800  		"listWithAnchors": {
   801  			input: []byte(`
   802  apiVersion: v1
   803  kind: DeploymentList
   804  items:
   805  - apiVersion: apps/v1
   806    kind: Deployment
   807    metadata:
   808      name: deployment-a
   809    spec: &hostAliases
   810      template:
   811        spec:
   812          hostAliases:
   813          - hostnames:
   814            - a.example.com
   815            ip: 8.8.8.8
   816  - apiVersion: apps/v1
   817    kind: Deployment
   818    metadata:
   819      name: deployment-b
   820    spec: *hostAliases
   821  `),
   822  			exp: expected{
   823  				sOut: []string{`
   824  apiVersion: v1
   825  kind: DeploymentList
   826  items:
   827  - apiVersion: apps/v1
   828    kind: Deployment
   829    metadata:
   830      name: deployment-a
   831    spec:
   832      template:
   833        spec:
   834          hostAliases:
   835          - hostnames:
   836            - a.example.com
   837            ip: 8.8.8.8
   838  - apiVersion: apps/v1
   839    kind: Deployment
   840    metadata:
   841      name: deployment-b
   842    spec:
   843      template:
   844        spec:
   845          hostAliases:
   846          - hostnames:
   847            - a.example.com
   848            ip: 8.8.8.8
   849  `},
   850  			},
   851  		},
   852  	}
   853  
   854  	for n := range testCases {
   855  		tc := testCases[n]
   856  		t.Run(n, func(t *testing.T) {
   857  			rNodes, err := FromBytes(tc.input)
   858  			if err != nil {
   859  				assert.True(t, tc.exp.isErr)
   860  				return
   861  			}
   862  			assert.False(t, tc.exp.isErr)
   863  			assert.Equal(t, len(tc.exp.sOut), len(rNodes))
   864  			for i, n := range rNodes {
   865  				json, err := n.String()
   866  				require.NoError(t, err)
   867  				assert.Equal(
   868  					t, strings.TrimSpace(tc.exp.sOut[i]),
   869  					strings.TrimSpace(json), n)
   870  			}
   871  		})
   872  	}
   873  }
   874  
   875  // Show the low level (go-yaml) representation of a small doc with a
   876  // YAML anchor and alias after reading it with anchor expansion on or off.
   877  func TestByteReader_AnchorsAweigh(t *testing.T) {
   878  	const input = `
   879  data:
   880    color: &color-used blue
   881    feeling: *color-used
   882  `
   883  	var rNode *yaml.RNode
   884  	{
   885  		rNodes, err := (&ByteReader{
   886  			OmitReaderAnnotations: true,
   887  			AnchorsAweigh:         false,
   888  			Reader:                bytes.NewBuffer([]byte(input)),
   889  		}).Read()
   890  		require.NoError(t, err)
   891  		assert.Equal(t, 1, len(rNodes))
   892  		rNode = rNodes[0]
   893  	}
   894  	// Confirm internal representation.
   895  	{
   896  		yNode := rNode.YNode()
   897  
   898  		// The high level object is a map of "data" to some value.
   899  		assert.Equal(t, yaml.NodeTagMap, yNode.Tag)
   900  
   901  		yNodes := yNode.Content
   902  		assert.Equal(t, 2, len(yNodes))
   903  
   904  		// Confirm that the key is "data".
   905  		assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
   906  		assert.Equal(t, "data", yNodes[0].Value)
   907  
   908  		assert.Equal(t, yaml.NodeTagMap, yNodes[1].Tag)
   909  
   910  		// The value of the "data" key.
   911  		yNodes = yNodes[1].Content
   912  		// Expect two name-value pairs.
   913  		assert.Equal(t, 4, len(yNodes))
   914  
   915  		assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
   916  		assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
   917  		assert.Equal(t, "color", yNodes[0].Value)
   918  		assert.Empty(t, yNodes[0].Anchor)
   919  		assert.Nil(t, yNodes[0].Alias)
   920  
   921  		assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
   922  		assert.Equal(t, yaml.NodeTagString, yNodes[1].Tag)
   923  		assert.Equal(t, "blue", yNodes[1].Value)
   924  		assert.Equal(t, "color-used", yNodes[1].Anchor)
   925  		assert.Nil(t, yNodes[1].Alias)
   926  
   927  		assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
   928  		assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
   929  		assert.Equal(t, "feeling", yNodes[2].Value)
   930  		assert.Empty(t, yNodes[2].Anchor)
   931  		assert.Nil(t, yNodes[2].Alias)
   932  
   933  		assert.Equal(t, yaml.AliasNode, yNodes[3].Kind)
   934  		assert.Empty(t, yNodes[3].Tag)
   935  		assert.Equal(t, "color-used", yNodes[3].Value)
   936  		assert.Empty(t, yNodes[3].Anchor)
   937  		assert.NotNil(t, yNodes[3].Alias)
   938  	}
   939  
   940  	str, err := rNode.String()
   941  	require.NoError(t, err)
   942  	// The string version matches the input (it still has anchors and aliases).
   943  	assert.Equal(t, strings.TrimSpace(input), strings.TrimSpace(str))
   944  
   945  	// Now do same thing again, but this time set AnchorsAweigh = true.
   946  	{
   947  		rNodes, err := (&ByteReader{
   948  			OmitReaderAnnotations: true,
   949  			AnchorsAweigh:         true,
   950  			Reader:                bytes.NewBuffer([]byte(input)),
   951  		}).Read()
   952  		require.NoError(t, err)
   953  		assert.Equal(t, 1, len(rNodes))
   954  		rNode = rNodes[0]
   955  	}
   956  	// Again make assertions on the internals.
   957  	{
   958  		yNode := rNode.YNode()
   959  
   960  		assert.Equal(t, yaml.NodeTagMap, yNode.Tag)
   961  
   962  		yNodes := yNode.Content
   963  		assert.Equal(t, 2, len(yNodes))
   964  
   965  		assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
   966  		assert.Equal(t, "data", yNodes[0].Value)
   967  
   968  		assert.Equal(t, yaml.NodeTagMap, yNodes[1].Tag)
   969  
   970  		yNodes = yNodes[1].Content
   971  		assert.Equal(t, 4, len(yNodes))
   972  
   973  		assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
   974  		assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
   975  		assert.Equal(t, "color", yNodes[0].Value)
   976  		assert.Empty(t, yNodes[0].Anchor)
   977  		assert.Nil(t, yNodes[0].Alias)
   978  
   979  		assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
   980  		assert.Equal(t, yaml.NodeTagString, yNodes[1].Tag)
   981  		assert.Equal(t, "blue", yNodes[1].Value)
   982  		assert.Empty(t, yNodes[1].Anchor)
   983  		assert.Nil(t, yNodes[1].Alias)
   984  
   985  		assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
   986  		assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
   987  		assert.Equal(t, "feeling", yNodes[2].Value)
   988  		assert.Empty(t, yNodes[2].Anchor)
   989  		assert.Nil(t, yNodes[2].Alias)
   990  
   991  		assert.Equal(t, yaml.ScalarNode, yNodes[3].Kind)
   992  		assert.Equal(t, yaml.NodeTagString, yNodes[3].Tag)
   993  		assert.Equal(t, "blue", yNodes[3].Value)
   994  		assert.Empty(t, yNodes[3].Anchor)
   995  		assert.Nil(t, yNodes[3].Alias)
   996  	}
   997  
   998  	str, err = rNode.String()
   999  	require.NoError(t, err)
  1000  	// This time, the alias has been replaced with the anchor definition.
  1001  	assert.Equal(t, strings.TrimSpace(`
  1002  data:
  1003    color: blue
  1004    feeling: blue
  1005  `), strings.TrimSpace(str))
  1006  }
  1007  
  1008  // TestByteReader_AddSeqIndentAnnotation tests if the internal.config.kubernetes.io/seqindent
  1009  // annotation is added to resources appropriately
  1010  func TestByteReader_AddSeqIndentAnnotation(t *testing.T) {
  1011  	type testCase struct {
  1012  		name                  string
  1013  		err                   string
  1014  		input                 string
  1015  		expectedAnnoValue     string
  1016  		OmitReaderAnnotations bool
  1017  	}
  1018  
  1019  	testCases := []testCase{
  1020  		{
  1021  			name: "read with wide indentation",
  1022  			input: `apiVersion: apps/v1
  1023  kind: Deployment
  1024  spec:
  1025    - foo
  1026    - bar
  1027    - baz
  1028  `,
  1029  			expectedAnnoValue: "wide",
  1030  		},
  1031  		{
  1032  			name: "read with compact indentation",
  1033  			input: `apiVersion: apps/v1
  1034  kind: Deployment
  1035  spec:
  1036  - foo
  1037  - bar
  1038  - baz
  1039  `,
  1040  			expectedAnnoValue: "compact",
  1041  		},
  1042  		{
  1043  			name: "read with mixed indentation, wide wins",
  1044  			input: `apiVersion: apps/v1
  1045  kind: Deployment
  1046  spec:
  1047    - foo
  1048    - bar
  1049    - baz
  1050  env:
  1051  - foo
  1052  - bar
  1053  `,
  1054  			expectedAnnoValue: "wide",
  1055  		},
  1056  		{
  1057  			name: "read with mixed indentation, compact wins",
  1058  			input: `apiVersion: apps/v1
  1059  kind: Deployment
  1060  spec:
  1061  - foo
  1062  - bar
  1063  - baz
  1064  env:
  1065    - foo
  1066    - bar
  1067  `,
  1068  			expectedAnnoValue: "compact",
  1069  		},
  1070  		{
  1071  			name: "error if conflicting options",
  1072  			input: `apiVersion: apps/v1
  1073  kind: Deployment
  1074  spec:
  1075  - foo
  1076  - bar
  1077  - baz
  1078  env:
  1079    - foo
  1080    - bar
  1081  `,
  1082  			OmitReaderAnnotations: true,
  1083  			err:                   `"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`,
  1084  		},
  1085  	}
  1086  
  1087  	for i := range testCases {
  1088  		tc := testCases[i]
  1089  		t.Run(tc.name, func(t *testing.T) {
  1090  			rNodes, err := (&ByteReader{
  1091  				OmitReaderAnnotations: tc.OmitReaderAnnotations,
  1092  				PreserveSeqIndent:     true,
  1093  				Reader:                bytes.NewBuffer([]byte(tc.input)),
  1094  			}).Read()
  1095  			if tc.err != "" {
  1096  				require.Error(t, err)
  1097  				assert.Equal(t, tc.err, err.Error())
  1098  				return
  1099  			}
  1100  			require.NoError(t, err)
  1101  			actual := rNodes[0].GetAnnotations()[kioutil.SeqIndentAnnotation]
  1102  			assert.Equal(t, tc.expectedAnnoValue, actual)
  1103  		})
  1104  	}
  1105  }
  1106  

View as plain text