...

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

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package filters_test
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	"sigs.k8s.io/kustomize/kyaml/kio"
    17  	. "sigs.k8s.io/kustomize/kyaml/kio/filters"
    18  	"sigs.k8s.io/kustomize/kyaml/kio/filters/testyaml"
    19  	"sigs.k8s.io/kustomize/kyaml/yaml"
    20  )
    21  
    22  func TestFormatInput_FixYaml1_1Compatibility(t *testing.T) {
    23  	y := `
    24  apiVersion: apps/v1
    25  kind: Deployment
    26  metadata:
    27    name: foo
    28    labels:
    29      foo: on
    30      foo2: hello1
    31    annotations:
    32      bar: 1
    33      bar2: hello2
    34  spec:
    35    template:
    36      spec:
    37        containers:
    38        - name: nginx
    39          image: nginx:1.0.0
    40          args:
    41          - on
    42          - 1
    43          - hello
    44          ports:
    45          - name: http
    46            targetPort: 80
    47            containerPort: 80
    48  `
    49  
    50  	// keep the style on values that parse as non-string types
    51  	expected := `apiVersion: apps/v1
    52  kind: Deployment
    53  metadata:
    54    name: foo
    55    labels:
    56      foo: "on"
    57      foo2: hello1
    58    annotations:
    59      bar: "1"
    60      bar2: hello2
    61  spec:
    62    template:
    63      spec:
    64        containers:
    65        - name: nginx
    66          image: nginx:1.0.0
    67          args:
    68          - "on"
    69          - "1"
    70          - hello
    71          ports:
    72          - name: http
    73            targetPort: 80
    74            containerPort: 80
    75  `
    76  
    77  	buff := &bytes.Buffer{}
    78  	err := kio.Pipeline{
    79  		Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
    80  		Filters: []kio.Filter{FormatFilter{
    81  			UseSchema: true,
    82  		}},
    83  		Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
    84  	}.Execute()
    85  	require.NoError(t, err)
    86  	assert.Equal(t, expected, buff.String())
    87  }
    88  
    89  func TestFormat_UnsortedInput_No_Schema(t *testing.T) {
    90  	y := `
    91  apiVersion: apps/v1
    92  spec:
    93    template:
    94      spec:
    95        containers:
    96        - name: nginx
    97          image: nginx:1.0.0
    98          args:
    99          - on
   100          - 1
   101          - hello
   102          ports:
   103          - name: http
   104            targetPort: 80
   105            containerPort: 80
   106  kind: Deployment
   107  metadata:
   108    name: foo
   109    labels:
   110      foo: on
   111      foo2: hello1
   112    annotations:
   113      bar: 1
   114      bar2: hello2
   115  `
   116  
   117  	// keep the style on values that parse as non-string types
   118  	expected := `apiVersion: apps/v1
   119  kind: Deployment
   120  metadata:
   121    name: foo
   122    labels:
   123      foo: on
   124      foo2: hello1
   125    annotations:
   126      bar: 1
   127      bar2: hello2
   128  spec:
   129    template:
   130      spec:
   131        containers:
   132        - name: nginx
   133          image: nginx:1.0.0
   134          args:
   135          - on
   136          - 1
   137          - hello
   138          ports:
   139          - name: http
   140            targetPort: 80
   141            containerPort: 80
   142  `
   143  
   144  	buff := &bytes.Buffer{}
   145  	err := kio.Pipeline{
   146  		Inputs:  []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
   147  		Filters: []kio.Filter{FormatFilter{}},
   148  		Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
   149  	}.Execute()
   150  	require.NoError(t, err)
   151  	assert.Equal(t, expected, buff.String())
   152  }
   153  
   154  func TestFormatInput_PostprocessStyle(t *testing.T) {
   155  	y := `
   156  apiVersion: v1
   157  kind: Foo
   158  metadata:
   159    name: foo
   160  spec:
   161    notBoolean: "true"
   162    notBoolean2: "on"
   163    isBoolean: on
   164    isBoolean2: true
   165    notInt: "12345"
   166    isInt: 12345
   167    isString1: hello world
   168    isString2: "hello world"
   169  `
   170  
   171  	// keep the style on values that parse as non-string types
   172  	expected := `apiVersion: v1
   173  kind: Foo
   174  metadata:
   175    name: foo
   176  spec:
   177    isBoolean: on
   178    isBoolean2: true
   179    isInt: 12345
   180    isString1: hello world
   181    isString2: hello world
   182    notBoolean: "true"
   183    notBoolean2: "on"
   184    notInt: "12345"
   185  `
   186  
   187  	buff := &bytes.Buffer{}
   188  	err := kio.Pipeline{
   189  		Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
   190  		Filters: []kio.Filter{FormatFilter{
   191  			UseSchema: true,
   192  			Process: func(n *yaml.Node) error {
   193  				if yaml.IsYaml1_1NonString(n) {
   194  					// don't change these styles, they are important for backwards compatibility
   195  					// e.g. "on" must remain quoted, on must remain unquoted
   196  					return nil
   197  				}
   198  				// style does not have semantic meaning
   199  				n.Style = 0
   200  				return nil
   201  			}}},
   202  		Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
   203  	}.Execute()
   204  	require.NoError(t, err)
   205  	assert.Equal(t, expected, buff.String())
   206  
   207  	y = `
   208  apiVersion: v1
   209  kind: Foo
   210  metadata:
   211    name: 'foo'
   212  spec:
   213    notBoolean: "true"
   214    notBoolean2: "on"
   215    notBoolean3: y is yes
   216    isBoolean: on
   217    isBoolean2: true
   218    isBoolean3: y
   219    notInt2: 1234 five
   220    notInt3: one 2345
   221    notInt: "12345"
   222    isInt1: 12345
   223    isInt2: -12345
   224    isFloat1: 1.1234
   225    isFloat2: 1.1234
   226    isString1: hello world
   227    isString2: "hello world"
   228    isString3: 'hello world'
   229  `
   230  
   231  	// keep the style on values that parse as non-string types
   232  	expected = `apiVersion: 'v1'
   233  kind: 'Foo'
   234  metadata:
   235    name: 'foo'
   236  spec:
   237    isBoolean: on
   238    isBoolean2: true
   239    isBoolean3: y
   240    isFloat1: 1.1234
   241    isFloat2: 1.1234
   242    isInt1: 12345
   243    isInt2: -12345
   244    isString1: 'hello world'
   245    isString2: 'hello world'
   246    isString3: 'hello world'
   247    notBoolean: "true"
   248    notBoolean2: "on"
   249    notBoolean3: 'y is yes'
   250    notInt: "12345"
   251    notInt2: '1234 five'
   252    notInt3: 'one 2345'
   253  `
   254  
   255  	buff = &bytes.Buffer{}
   256  	err = kio.Pipeline{
   257  		Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
   258  		Filters: []kio.Filter{FormatFilter{
   259  			UseSchema: true,
   260  			Process: func(n *yaml.Node) error {
   261  				if yaml.IsYaml1_1NonString(n) {
   262  					// don't change these styles, they are important for backwards compatibility
   263  					// e.g. "on" must remain quoted, on must remain unquoted
   264  					return nil
   265  				}
   266  				// style does not have semantic meaning
   267  				n.Style = yaml.SingleQuotedStyle
   268  				return nil
   269  			}}},
   270  		Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
   271  	}.Execute()
   272  	require.NoError(t, err)
   273  	assert.Equal(t, expected, buff.String())
   274  }
   275  
   276  func TestFormatInput_Style(t *testing.T) {
   277  	y := `
   278  apiVersion: v1
   279  kind: Foo
   280  metadata:
   281    name: foo
   282  spec:
   283    notBoolean: "true"
   284    notBoolean2: "on"
   285    isBoolean: on
   286    isBoolean2: true
   287  `
   288  
   289  	expected := `apiVersion: v1
   290  kind: Foo
   291  metadata:
   292    name: foo
   293  spec:
   294    isBoolean: on
   295    isBoolean2: true
   296    notBoolean: "true"
   297    notBoolean2: "on"
   298  `
   299  
   300  	s, err := FormatInput(strings.NewReader(y))
   301  	require.NoError(t, err)
   302  	assert.Equal(t, expected, s.String())
   303  }
   304  
   305  // TestFormatInput_configMap verifies a ConfigMap yaml is formatted correctly
   306  func TestFormatInput_configMap(t *testing.T) {
   307  	y := `
   308  
   309  
   310  # this formatting is intentionally weird
   311  
   312  apiVersion: v1
   313  # this is data
   314  data:
   315    # this is color
   316    color: purple
   317    # that was color
   318  
   319    # this is textmode
   320    textmode: "true"
   321    # this is how
   322    how: fairlyNice
   323  
   324  
   325  
   326  kind: ConfigMap
   327  
   328  
   329  metadata:
   330    selfLink: /api/v1/namespaces/default/configmaps/config-multi-env-files
   331    namespace: default
   332    creationTimestamp: 2017-12-27T18:38:34Z
   333    name: config-multi-env-files
   334    resourceVersion: "810136"
   335    uid: 252c4572-eb35-11e7-887b-42010a8002b8  # keep no trailing linefeed`
   336  
   337  	expected := `# this formatting is intentionally weird
   338  
   339  apiVersion: v1
   340  kind: ConfigMap
   341  metadata:
   342    name: config-multi-env-files
   343    namespace: default
   344    creationTimestamp: 2017-12-27T18:38:34Z
   345    resourceVersion: "810136"
   346    selfLink: /api/v1/namespaces/default/configmaps/config-multi-env-files
   347    uid: 252c4572-eb35-11e7-887b-42010a8002b8 # keep no trailing linefeed
   348  # this is data
   349  data:
   350    # this is color
   351    color: purple
   352    # that was color
   353  
   354    # this is how
   355    how: fairlyNice
   356    # this is textmode
   357    textmode: "true"
   358  `
   359  
   360  	s, err := FormatInput(strings.NewReader(y))
   361  	require.NoError(t, err)
   362  	assert.Equal(t, expected, s.String())
   363  }
   364  
   365  // TestFormatInput_deployment verifies a Deployment yaml is formatted correctly
   366  func TestFormatInput_deployment(t *testing.T) {
   367  	y := `
   368  apiVersion: apps/v1
   369  kind: Deployment
   370  metadata:
   371    name: nginx-deployment
   372    labels:
   373      app: nginx
   374  spec:
   375    selector:
   376      matchLabels:
   377        app: nginx
   378    replicas: 3
   379    template:
   380      metadata:
   381        labels:
   382          app: nginx
   383      spec:
   384        containers:
   385        # this is a container
   386        - ports:
   387          # this is a port
   388          - containerPort: 80
   389          name: b-nginx
   390          image: nginx:1.7.9
   391        # this is another container
   392        - name: a-nginx
   393          image: nginx:1.7.9
   394          ports:
   395          - containerPort: 80
   396  `
   397  	expected := `apiVersion: apps/v1
   398  kind: Deployment
   399  metadata:
   400    name: nginx-deployment
   401    labels:
   402      app: nginx
   403  spec:
   404    replicas: 3
   405    selector:
   406      matchLabels:
   407        app: nginx
   408    template:
   409      metadata:
   410        labels:
   411          app: nginx
   412      spec:
   413        containers:
   414        # this is another container
   415        - name: a-nginx
   416          image: nginx:1.7.9
   417          ports:
   418          - containerPort: 80
   419        # this is a container
   420        - name: b-nginx
   421          image: nginx:1.7.9
   422          ports:
   423          # this is a port
   424          - containerPort: 80
   425  `
   426  	s, err := FormatInput(strings.NewReader(y))
   427  	require.NoError(t, err)
   428  	assert.Equal(t, expected, s.String())
   429  }
   430  
   431  // TestFormatInput_service verifies a Service yaml is formatted correctly
   432  func TestFormatInput_service(t *testing.T) {
   433  	y := `
   434  apiVersion: v1
   435  kind: Service
   436  metadata:
   437    name: my-service
   438  spec:
   439    selector:
   440      app: MyApp
   441    ports:
   442      - protocol: TCP
   443        port: 80
   444        targetPort: 9376
   445  `
   446  	expected := `apiVersion: v1
   447  kind: Service
   448  metadata:
   449    name: my-service
   450  spec:
   451    selector:
   452      app: MyApp
   453    ports:
   454    - protocol: TCP
   455      port: 80
   456      targetPort: 9376
   457  `
   458  	s, err := FormatInput(strings.NewReader(y))
   459  	require.NoError(t, err)
   460  	assert.Equal(t, expected, s.String())
   461  }
   462  
   463  // TestFormatInput_service verifies a Service yaml is formatted correctly
   464  func TestFormatInput_validatingWebhookConfiguration(t *testing.T) {
   465  	y := `
   466  apiVersion: admissionregistration.k8s.io/v1
   467  kind: ValidatingWebhookConfiguration
   468  metadata:
   469    name: <name of this configuration object>
   470  webhooks:
   471  - name: <webhook name, e.g., pod-policy.example.io>
   472    rules:
   473    - apiGroups:
   474      - ""
   475      apiVersions:
   476      - v1
   477      operations:
   478        - UPDATE # this list is indented by 2
   479        - CREATE
   480        - CONNECT
   481      resources:
   482      - pods # this list is not indented by 2
   483      scope: "Namespaced"
   484    clientConfig:
   485      service:
   486        namespace: <namespace of the front-end service>
   487        name: <name of the front-end service>
   488      caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
   489    admissionReviewVersions:
   490    - v1beta1
   491    timeoutSeconds: 1
   492  `
   493  	expected := `apiVersion: admissionregistration.k8s.io/v1
   494  kind: ValidatingWebhookConfiguration
   495  metadata:
   496    name: <name of this configuration object>
   497  webhooks:
   498  - name: <webhook name, e.g., pod-policy.example.io>
   499    admissionReviewVersions:
   500    - v1beta1
   501    clientConfig:
   502      service:
   503        name: <name of the front-end service>
   504        namespace: <namespace of the front-end service>
   505      caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
   506    rules:
   507    - resources:
   508      - pods # this list is not indented by 2
   509      apiGroups:
   510      - ""
   511      apiVersions:
   512      - v1
   513      operations:
   514      - CONNECT
   515      - CREATE
   516      - UPDATE # this list is indented by 2
   517      scope: "Namespaced"
   518    timeoutSeconds: 1
   519  `
   520  	s, err := FormatInput(strings.NewReader(y))
   521  	require.NoError(t, err)
   522  	assert.Equal(t, expected, s.String())
   523  }
   524  
   525  // TestFormatInput_unKnownType verifies an unknown type yaml is formatted correctly
   526  func TestFormatInput_unKnownType(t *testing.T) {
   527  	y := `
   528  spec:
   529    template:
   530      spec:
   531        # these shouldn't be sorted because the type isn't whitelisted
   532        containers:
   533        - name: b
   534        - name: a
   535    replicas: 1
   536  status:
   537    conditions:
   538    - 3
   539    - 1
   540    - 2
   541  other:
   542    b: a1
   543    a: b1
   544  apiVersion: example.com/v1beta1
   545  kind: MyType
   546  `
   547  
   548  	expected := `apiVersion: example.com/v1beta1
   549  kind: MyType
   550  spec:
   551    replicas: 1
   552    template:
   553      spec:
   554        # these shouldn't be sorted because the type isn't whitelisted
   555        containers:
   556        - name: b
   557        - name: a
   558  status:
   559    conditions:
   560    - 3
   561    - 1
   562    - 2
   563  other:
   564    a: b1
   565    b: a1
   566  `
   567  	s, err := FormatInput(strings.NewReader(y))
   568  	require.NoError(t, err)
   569  	assert.Equal(t, expected, s.String())
   570  }
   571  
   572  // TestFormatInput_deployment verifies a Deployment yaml is formatted correctly
   573  func TestFormatInput_resources(t *testing.T) {
   574  	input := &bytes.Buffer{}
   575  	_, err := io.Copy(input, bytes.NewReader(testyaml.UnformattedYaml1))
   576  	require.NoError(t, err)
   577  	_, err = io.Copy(input, strings.NewReader("---\n"))
   578  	require.NoError(t, err)
   579  	_, err = io.Copy(input, bytes.NewReader(testyaml.UnformattedYaml2))
   580  	require.NoError(t, err)
   581  	_, err = io.Copy(input, strings.NewReader("---\n"))
   582  	require.NoError(t, err)
   583  	_, err = io.Copy(input, bytes.NewReader(testyaml.UnformattedYaml3))
   584  	require.NoError(t, err)
   585  
   586  	expectedOutput := &bytes.Buffer{}
   587  	_, err = io.Copy(expectedOutput, bytes.NewReader(testyaml.FormattedYaml1))
   588  	require.NoError(t, err)
   589  	_, err = io.Copy(expectedOutput, strings.NewReader("---\n"))
   590  	require.NoError(t, err)
   591  	_, err = io.Copy(expectedOutput, bytes.NewReader(testyaml.FormattedYaml2))
   592  	require.NoError(t, err)
   593  	_, err = io.Copy(expectedOutput, strings.NewReader("---\n"))
   594  	require.NoError(t, err)
   595  	_, err = io.Copy(expectedOutput, bytes.NewReader(testyaml.FormattedYaml3))
   596  	require.NoError(t, err)
   597  
   598  	s, err := FormatInput(input)
   599  	require.NoError(t, err)
   600  	assert.Equal(t, expectedOutput.String(), s.String())
   601  }
   602  
   603  func TestFormatInput_failMissingKind(t *testing.T) {
   604  	y := `
   605  spec:
   606    template:
   607      spec:
   608        containers:
   609        - b
   610        - a
   611    replicas: 1
   612  status:
   613    conditions:
   614    - 3
   615    - 1
   616    - 2
   617  other:
   618    b: a1
   619    a: b1
   620  apiVersion: example.com/v1beta1
   621  `
   622  
   623  	b, err := FormatInput(strings.NewReader(y))
   624  	require.NoError(t, err)
   625  	assert.Equal(t, strings.TrimLeft(y, "\n"), b.String())
   626  }
   627  
   628  func TestFormatInput_failMissingApiVersion(t *testing.T) {
   629  	y := `
   630  spec:
   631    template:
   632      spec:
   633        containers:
   634        - a
   635        - b
   636    replicas: 1
   637  status:
   638    conditions:
   639    - 3
   640    - 1
   641    - 2
   642  other:
   643    b: a1
   644    a: b1
   645  kind: MyKind
   646  `
   647  
   648  	b, err := FormatInput(strings.NewReader(y))
   649  	require.NoError(t, err)
   650  	assert.Equal(t, strings.TrimLeft(y, "\n"), b.String())
   651  }
   652  
   653  func TestFormatInput_failUnmarshal(t *testing.T) {
   654  	y := `
   655  spec:
   656    template:
   657      spec:
   658        containers:
   659        - a
   660        - b
   661    replicas: 1
   662  status:
   663    conditions:
   664    - 3
   665    - 1
   666    - 2
   667  other:
   668  	b: a1
   669  	a: b1
   670  kind: MyKind
   671  apiVersion: example.com/v1beta1
   672  `
   673  
   674  	_, err := FormatInput(strings.NewReader(y))
   675  	assert.EqualError(t, err, "MalformedYAMLError: yaml: line 15: found character that cannot start any token")
   676  }
   677  
   678  // TestFormatFileOrDirectory_yamlExtFile verifies that FormatFileOrDirectory will format a file
   679  // with a .yaml extension.
   680  func TestFormatFileOrDirectory_yamlExtFile(t *testing.T) {
   681  	// write the unformatted file
   682  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   683  	if !assert.NoError(t, err) {
   684  		return
   685  	}
   686  	defer os.Remove(f.Name())
   687  	err = os.WriteFile(f.Name(), testyaml.UnformattedYaml1, 0600)
   688  	if !assert.NoError(t, err) {
   689  		return
   690  	}
   691  
   692  	// format the file
   693  	err = FormatFileOrDirectory(f.Name())
   694  	if !assert.NoError(t, err) {
   695  		return
   696  	}
   697  
   698  	// check the result is formatted
   699  	b, err := os.ReadFile(f.Name())
   700  	if !assert.NoError(t, err) {
   701  		return
   702  	}
   703  	assert.Equal(t, string(testyaml.FormattedYaml1), string(b))
   704  }
   705  
   706  func TestFormatFileOrDirectory_multipleYamlEntries(t *testing.T) {
   707  	// write the unformatted file
   708  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   709  	require.NoError(t, err)
   710  	defer os.Remove(f.Name())
   711  	err = os.WriteFile(f.Name(),
   712  		[]byte(string(testyaml.UnformattedYaml1)+"---\n"+string(testyaml.UnformattedYaml2)), 0600)
   713  	require.NoError(t, err)
   714  
   715  	// format the file
   716  	err = FormatFileOrDirectory(f.Name())
   717  	require.NoError(t, err)
   718  
   719  	// check the result is formatted
   720  	b, err := os.ReadFile(f.Name())
   721  	require.NoError(t, err)
   722  	assert.Equal(t, string(testyaml.FormattedYaml1)+"---\n"+string(testyaml.FormattedYaml2), string(b))
   723  }
   724  
   725  // TestFormatFileOrDirectory_ymlExtFile verifies that FormatFileOrDirectory will format a file
   726  // with a .yml extension.
   727  func TestFormatFileOrDirectory_ymlExtFile(t *testing.T) {
   728  	// write the unformatted file
   729  	f, err := os.CreateTemp("", "yamlfmt*.yml")
   730  	require.NoError(t, err)
   731  	defer os.Remove(f.Name())
   732  	err = os.WriteFile(f.Name(), testyaml.UnformattedYaml1, 0600)
   733  	require.NoError(t, err)
   734  
   735  	// format the file
   736  	err = FormatFileOrDirectory(f.Name())
   737  	require.NoError(t, err)
   738  
   739  	// check the result is formatted
   740  	b, err := os.ReadFile(f.Name())
   741  	require.NoError(t, err)
   742  	assert.Equal(t, string(testyaml.FormattedYaml1), string(b))
   743  }
   744  
   745  // TestFormatFileOrDirectory_YamlExtFileWithJson verifies that the JSON compatible flow style
   746  // YAML content is formatted as such.
   747  func TestFormatFileOrDirectory_YamlExtFileWithJson(t *testing.T) {
   748  	// write the unformatted JSON file contents
   749  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   750  	require.NoError(t, err)
   751  	defer os.Remove(f.Name())
   752  	err = os.WriteFile(f.Name(), testyaml.UnformattedJSON1, 0600)
   753  	require.NoError(t, err)
   754  
   755  	// format the file
   756  	err = FormatFileOrDirectory(f.Name())
   757  	require.NoError(t, err)
   758  
   759  	// check the result is formatted as yaml
   760  	b, err := os.ReadFile(f.Name())
   761  	require.NoError(t, err)
   762  	assert.Equal(t, string(testyaml.FormattedFlowYAML1), string(b))
   763  }
   764  
   765  // TestFormatFileOrDirectory_JsonExtFileWithNotModified verifies that a file with .json extensions
   766  // and JSON contents won't be modified.
   767  func TestFormatFileOrDirectory_JsonExtFileWithNotModified(t *testing.T) {
   768  	// write the unformatted JSON file contents
   769  	f, err := os.CreateTemp("", "yamlfmt*.json")
   770  	require.NoError(t, err)
   771  	defer os.Remove(f.Name())
   772  	err = os.WriteFile(f.Name(), testyaml.UnformattedJSON1, 0600)
   773  	require.NoError(t, err)
   774  
   775  	// format the file
   776  	err = FormatFileOrDirectory(f.Name())
   777  	require.NoError(t, err)
   778  
   779  	// check the result is formatted as yaml
   780  	b, err := os.ReadFile(f.Name())
   781  	require.NoError(t, err)
   782  	assert.Equal(t, string(testyaml.UnformattedJSON1), string(b))
   783  }
   784  
   785  // TestFormatFileOrDirectory_partialKubernetesYamlFile verifies that if a yaml file contains both
   786  // Kubernetes and non-Kubernetes documents, it will only format the Kubernetes documents
   787  func TestFormatFileOrDirectory_partialKubernetesYamlFile(t *testing.T) {
   788  	// write the unformatted file
   789  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   790  	require.NoError(t, err)
   791  	defer os.Remove(f.Name())
   792  	err = os.WriteFile(f.Name(), []byte(string(testyaml.UnformattedYaml1)+`---
   793  status:
   794    conditions:
   795    - 3
   796    - 1
   797    - 2
   798  spec: a
   799  ---
   800  `+string(testyaml.UnformattedYaml2)), 0600)
   801  	require.NoError(t, err)
   802  
   803  	// format the file
   804  	err = FormatFileOrDirectory(f.Name())
   805  	require.NoError(t, err)
   806  
   807  	// check the result is  NOT formatted
   808  	b, err := os.ReadFile(f.Name())
   809  	require.NoError(t, err)
   810  	assert.Equal(t, string(testyaml.FormattedYaml1)+`---
   811  status:
   812    conditions:
   813    - 3
   814    - 1
   815    - 2
   816  spec: a
   817  ---
   818  `+string(testyaml.FormattedYaml2), string(b))
   819  }
   820  
   821  // TestFormatFileOrDirectory_nonKubernetesYamlFile verifies that if a yaml file does not contain
   822  // kubernetes
   823  func TestFormatFileOrDirectory_skipNonKubernetesYamlFile(t *testing.T) {
   824  	// write the unformatted JSON file contents
   825  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   826  	require.NoError(t, err)
   827  	defer os.Remove(f.Name())
   828  	err = os.WriteFile(f.Name(), []byte(`
   829  status:
   830    conditions:
   831    - 3
   832    - 1
   833    - 2
   834  spec: a
   835  `), 0600)
   836  	require.NoError(t, err)
   837  
   838  	// format the file
   839  	err = FormatFileOrDirectory(f.Name())
   840  	require.NoError(t, err)
   841  
   842  	// check the result is formatted as yaml
   843  	b, err := os.ReadFile(f.Name())
   844  	require.NoError(t, err)
   845  	assert.Equal(t, `status:
   846    conditions:
   847    - 3
   848    - 1
   849    - 2
   850  spec: a
   851  `, string(b))
   852  }
   853  
   854  // TestFormatFileOrDirectory_jsonFile should not fmt the file even though it contains yaml.
   855  func TestFormatFileOrDirectory_skipJsonExtFile(t *testing.T) {
   856  	f, err := os.CreateTemp("", "yamlfmt*.json")
   857  	require.NoError(t, err)
   858  	defer os.Remove(f.Name())
   859  	err = os.WriteFile(f.Name(), testyaml.UnformattedYaml1, 0600)
   860  	require.NoError(t, err)
   861  
   862  	err = FormatFileOrDirectory(f.Name())
   863  	require.NoError(t, err)
   864  
   865  	b, err := os.ReadFile(f.Name())
   866  	require.NoError(t, err)
   867  
   868  	assert.Equal(t, string(testyaml.UnformattedYaml1), string(b))
   869  }
   870  
   871  // TestFormatFileOrDirectory_directory verifies that yaml files will be formatted,
   872  // and other files will be ignored
   873  func TestFormatFileOrDirectory_directory(t *testing.T) {
   874  	d := t.TempDir()
   875  
   876  	err := os.Mkdir(filepath.Join(d, "config"), 0700)
   877  	require.NoError(t, err)
   878  
   879  	err = os.WriteFile(filepath.Join(d, "c1.yaml"), testyaml.UnformattedYaml1, 0600)
   880  	require.NoError(t, err)
   881  
   882  	err = os.WriteFile(filepath.Join(d, "config", "c2.yaml"), testyaml.UnformattedYaml2, 0600)
   883  	require.NoError(t, err)
   884  
   885  	err = os.WriteFile(filepath.Join(d, "README.md"), []byte(`# Markdown`), 0600)
   886  	require.NoError(t, err)
   887  
   888  	err = FormatFileOrDirectory(d)
   889  	require.NoError(t, err)
   890  
   891  	b, err := os.ReadFile(filepath.Join(d, "c1.yaml"))
   892  	require.NoError(t, err)
   893  	assert.Equal(t, string(testyaml.FormattedYaml1), string(b))
   894  
   895  	b, err = os.ReadFile(filepath.Join(d, "config", "c2.yaml"))
   896  	require.NoError(t, err)
   897  	assert.Equal(t, string(testyaml.FormattedYaml2), string(b))
   898  
   899  	b, err = os.ReadFile(filepath.Join(d, "README.md"))
   900  	require.NoError(t, err)
   901  	assert.Equal(t, `# Markdown`, string(b))
   902  
   903  	// verify no additional files were created
   904  	files := []string{
   905  		".", "c1.yaml", "README.md", "config", filepath.Join("config", "c2.yaml")}
   906  	err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
   907  		require.NoError(t, err)
   908  		path, err = filepath.Rel(d, path)
   909  		require.NoError(t, err)
   910  		assert.Contains(t, files, path)
   911  		return nil
   912  	})
   913  	require.NoError(t, err)
   914  }
   915  
   916  // TestFormatFileOrDirectory_trimWhiteSpace verifies that trailling and leading whitespace is
   917  // trimmed
   918  func TestFormatFileOrDirectory_trimWhiteSpace(t *testing.T) {
   919  	f, err := os.CreateTemp("", "yamlfmt*.yaml")
   920  	require.NoError(t, err)
   921  	defer os.Remove(f.Name())
   922  	err = os.WriteFile(f.Name(), []byte("\n\n"+string(testyaml.UnformattedYaml1)+"\n\n"), 0600)
   923  	require.NoError(t, err)
   924  
   925  	err = FormatFileOrDirectory(f.Name())
   926  	require.NoError(t, err)
   927  
   928  	b, err := os.ReadFile(f.Name())
   929  	require.NoError(t, err)
   930  
   931  	assert.Equal(t, string(testyaml.FormattedYaml1), string(b))
   932  }
   933  
   934  func TestFormatFileOrDirectory_FmtAnnotation(t *testing.T) {
   935  	testCases := []struct {
   936  		name           string
   937  		input          []byte
   938  		expectedOutput []byte
   939  		expectError    bool
   940  	}{
   941  		{
   942  			name:           "no formatting annotation",
   943  			input:          testyaml.UnformattedYaml1,
   944  			expectedOutput: testyaml.FormattedYaml1,
   945  		},
   946  		{
   947  			name: "formatting strategy none",
   948  			input: []byte(`
   949  spec: a
   950  status:
   951    conditions:
   952    - 3
   953    - 1
   954    - 2
   955  apiVersion: example.com/v1beta1
   956  kind: MyType
   957  metadata:
   958    annotations:
   959      config.kubernetes.io/formatting: none
   960  `),
   961  			expectedOutput: []byte(`
   962  spec: a
   963  status:
   964    conditions:
   965    - 3
   966    - 1
   967    - 2
   968  apiVersion: example.com/v1beta1
   969  kind: MyType
   970  metadata:
   971    annotations:
   972      config.kubernetes.io/formatting: none
   973  `),
   974  		},
   975  		{
   976  			name: "formatting strategy standard",
   977  			input: []byte(`
   978  spec: a
   979  status:
   980    conditions:
   981    - 3
   982    - 1
   983    - 2
   984  apiVersion: example.com/v1beta1
   985  kind: MyType
   986  metadata:
   987    annotations:
   988      config.kubernetes.io/formatting: standard
   989  `),
   990  			expectedOutput: []byte(`
   991  apiVersion: example.com/v1beta1
   992  kind: MyType
   993  metadata:
   994    annotations:
   995      config.kubernetes.io/formatting: standard
   996  spec: a
   997  status:
   998    conditions:
   999    - 3
  1000    - 1
  1001    - 2
  1002  `),
  1003  		},
  1004  		{
  1005  			name: "unknown formatting strategy",
  1006  			input: []byte(`
  1007  spec: a
  1008  status:
  1009    conditions:
  1010    - 3
  1011    - 1
  1012    - 2
  1013  apiVersion: example.com/v1beta1
  1014  kind: MyType
  1015  metadata:
  1016    annotations:
  1017      config.kubernetes.io/formatting: unknown
  1018  `),
  1019  			expectError: true,
  1020  		},
  1021  	}
  1022  
  1023  	for i := range testCases {
  1024  		test := testCases[i]
  1025  		t.Run(test.name, func(t *testing.T) {
  1026  			f, err := os.CreateTemp("", "yamlfmt*.yaml")
  1027  			require.NoError(t, err)
  1028  			defer os.Remove(f.Name())
  1029  
  1030  			err = os.WriteFile(f.Name(), test.input, 0600)
  1031  			require.NoError(t, err)
  1032  
  1033  			err = FormatFileOrDirectory(f.Name())
  1034  			if test.expectError {
  1035  				require.Error(t, err)
  1036  				return
  1037  			}
  1038  			require.NoError(t, err)
  1039  
  1040  			b, err := os.ReadFile(f.Name())
  1041  			require.NoError(t, err)
  1042  
  1043  			assert.Equal(t, strings.TrimSpace(string(test.expectedOutput)),
  1044  				strings.TrimSpace(string(b)))
  1045  		})
  1046  	}
  1047  }
  1048  
  1049  func TestFormatInput_NullCases(t *testing.T) {
  1050  	y := `
  1051  apiVersion: v1
  1052  kind: Service
  1053  metadata:
  1054    name: nginx
  1055    labels:
  1056      app: null
  1057  spec:
  1058    selector:
  1059      app: nginx
  1060    ports:
  1061    - name: http
  1062      port: 80
  1063      targetPort: ~
  1064      nodePort: null
  1065    allocateLoadBalancerNodePorts: null
  1066  `
  1067  
  1068  	// keep the style on values that parse as non-string types
  1069  	expected := `apiVersion: v1
  1070  kind: Service
  1071  metadata:
  1072    name: nginx
  1073    labels:
  1074      app: null
  1075  spec:
  1076    selector:
  1077      app: nginx
  1078    ports:
  1079    - name: http
  1080      port: 80
  1081      targetPort: ~
  1082      nodePort: null
  1083    allocateLoadBalancerNodePorts: null
  1084  `
  1085  
  1086  	buff := &bytes.Buffer{}
  1087  	err := kio.Pipeline{
  1088  		Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
  1089  		Filters: []kio.Filter{FormatFilter{
  1090  			UseSchema: true,
  1091  		}},
  1092  		Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
  1093  	}.Execute()
  1094  	require.NoError(t, err)
  1095  	assert.Equal(t, expected, buff.String())
  1096  }
  1097  

View as plain text