sigs.k8s.io/cluster-api@v1.7.1/util/yaml/yaml_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package yaml
    18  
    19  import (
    20  	"os"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    27  )
    28  
    29  const validCluster = `
    30  apiVersion: "cluster.x-k8s.io/v1beta1"
    31  kind: Cluster
    32  metadata:
    33    name: cluster1
    34  spec:`
    35  
    36  const validMachines1 = `
    37  ---
    38  apiVersion: "cluster.x-k8s.io/v1beta1"
    39  kind: Machine
    40  metadata:
    41    name: machine1
    42  ---
    43  apiVersion: "cluster.x-k8s.io/v1beta1"
    44  kind: Machine
    45  metadata:
    46    name: machine2`
    47  
    48  const validUnified1 = `
    49  apiVersion: "cluster.x-k8s.io/v1beta1"
    50  kind: Cluster
    51  metadata:
    52    name: cluster1
    53  ---
    54  apiVersion: "cluster.x-k8s.io/v1beta1"
    55  kind: Machine
    56  metadata:
    57    name: machine1`
    58  
    59  const validUnified2 = `
    60  apiVersion: "cluster.x-k8s.io/v1beta1"
    61  kind: Cluster
    62  metadata:
    63    name: cluster1
    64  ---
    65  apiVersion: "cluster.x-k8s.io/v1beta1"
    66  kind: Machine
    67  metadata:
    68    name: machine1
    69  ---
    70  apiVersion: "cluster.x-k8s.io/v1beta1"
    71  kind: Machine
    72  metadata:
    73    name: machine2`
    74  
    75  const validUnified3 = `
    76  apiVersion: v1
    77  data:
    78    cluster_name: cluster1
    79    cluster_network_pods_cidrBlock: 192.168.0.0/16
    80    cluster_network_services_cidrBlock: 10.96.0.0/12
    81    cluster_sshKeyName: default
    82  kind: ConfigMap
    83  metadata:
    84    name: cluster-api-shared-configuration
    85    namespace: cluster-api-test
    86  ---
    87  apiVersion: "cluster.x-k8s.io/v1beta1"
    88  kind: Cluster
    89  metadata:
    90    name: cluster1
    91  ---
    92  apiVersion: "cluster.x-k8s.io/v1beta1"
    93  kind: Machine
    94  metadata:
    95    name: machine1
    96  ---
    97  apiVersion: "cluster.x-k8s.io/v1beta1"
    98  kind: Machine
    99  metadata:
   100    name: machine2`
   101  
   102  const invalidMachines1 = `
   103  items:
   104  - apiVersion: "cluster.x-k8s.io/v1beta1"
   105    kind: Machine
   106    metadata:
   107      name: machine1
   108    spec:`
   109  
   110  const invalidMachines2 = `
   111  items:
   112  - metadata:
   113      name: machine1
   114    spec:
   115  - metadata:
   116      name: machine2
   117  `
   118  
   119  const invalidUnified1 = `
   120  apiVersion: v1
   121  data:
   122    cluster_name: cluster1
   123    cluster_network_pods_cidrBlock: 192.168.0.0/16
   124    cluster_network_services_cidrBlock: 10.96.0.0/12
   125    cluster_sshKeyName: default
   126  kind: ConfigMap
   127  metadata:
   128    name: cluster-api-shared-configuration
   129    namespace: cluster-api-test
   130  ---
   131  apiVersion: "cluster.x-k8s.io/v1beta1"
   132  kind: Cluster
   133  metadata:
   134    name: cluster1
   135  ---
   136  apiVersion: "cluster.x-k8s.io/v1beta1"
   137  kind: Machine
   138  - metadata:
   139      name: machine1
   140    spec:
   141  - metadata:
   142      name: machine2
   143  `
   144  
   145  const invalidUnified2 = `
   146  apiVersion: v1
   147  data:
   148    cluster_name: cluster1
   149    cluster_network_pods_cidrBlock: 192.168.0.0/16
   150    cluster_network_services_cidrBlock: 10.96.0.0/12
   151    cluster_sshKeyName: default
   152  kind: ConfigMap
   153  metadata:
   154    name: cluster-api-shared-configuration
   155    namespace: cluster-api-test
   156  ---
   157  apiVersion: "cluster.x-k8s.io/v1beta1"
   158  kind: Cluster
   159  metadata:
   160    name: cluster1
   161  ---
   162  - metadata:
   163      name: machine1
   164    spec:
   165  - metadata:
   166  		name: machine2
   167  `
   168  
   169  func TestParseInvalidFile(t *testing.T) {
   170  	g := NewWithT(t)
   171  
   172  	_, err := Parse(ParseInput{
   173  		File: "filedoesnotexist",
   174  	})
   175  	g.Expect(err).To(HaveOccurred())
   176  }
   177  
   178  func TestParseClusterYaml(t *testing.T) {
   179  	g := NewWithT(t)
   180  
   181  	var testcases = []struct {
   182  		name         string
   183  		contents     string
   184  		expectedName string
   185  		expectErr    bool
   186  	}{
   187  		{
   188  			name:         "valid file",
   189  			contents:     validCluster,
   190  			expectedName: "cluster1",
   191  		},
   192  		{
   193  			name:         "valid unified file",
   194  			contents:     validUnified1,
   195  			expectedName: "cluster1",
   196  		},
   197  		{
   198  			name:         "valid unified file with separate machines",
   199  			contents:     validUnified2,
   200  			expectedName: "cluster1",
   201  		},
   202  		{
   203  			name:         "valid unified file with separate machines and a configmap",
   204  			contents:     validUnified3,
   205  			expectedName: "cluster1",
   206  		},
   207  		{
   208  			name:      "gibberish in file",
   209  			contents:  `blah ` + validCluster + ` blah`,
   210  			expectErr: true,
   211  		},
   212  	}
   213  	for _, testcase := range testcases {
   214  		t.Run(testcase.name, func(*testing.T) {
   215  			file, err := createTempFile(testcase.contents)
   216  			g.Expect(err).ToNot(HaveOccurred())
   217  			defer os.Remove(file)
   218  
   219  			c, err := Parse(ParseInput{File: file})
   220  			if testcase.expectErr {
   221  				g.Expect(err).To(HaveOccurred())
   222  				return
   223  			}
   224  
   225  			g.Expect(err).ToNot(HaveOccurred())
   226  			g.Expect(c.Clusters).NotTo(BeEmpty())
   227  			g.Expect(c.Clusters[0].Name).To(Equal(testcase.expectedName))
   228  		})
   229  	}
   230  }
   231  
   232  func TestParseMachineYaml(t *testing.T) {
   233  	g := NewWithT(t)
   234  
   235  	var testcases = []struct {
   236  		name                 string
   237  		contents             string
   238  		expectErr            bool
   239  		expectedMachineCount int
   240  	}{
   241  		{
   242  			name:                 "valid file using Machines",
   243  			contents:             validMachines1,
   244  			expectedMachineCount: 2,
   245  		},
   246  		{
   247  			name:                 "valid unified file with machine list",
   248  			contents:             validUnified1,
   249  			expectedMachineCount: 1,
   250  		},
   251  		{
   252  			name:                 "valid unified file with separate machines",
   253  			contents:             validUnified2,
   254  			expectedMachineCount: 2,
   255  		},
   256  		{
   257  			name:                 "valid unified file with separate machines and a configmap",
   258  			contents:             validUnified3,
   259  			expectedMachineCount: 2,
   260  		},
   261  		{
   262  			name:      "invalid file",
   263  			contents:  invalidMachines1,
   264  			expectErr: true,
   265  		},
   266  		{
   267  			name:      "invalid file without type info",
   268  			contents:  invalidMachines2,
   269  			expectErr: true,
   270  		},
   271  		{
   272  			name:      "valid unified for cluster with invalid Machine (only with type info) and a configmap",
   273  			contents:  invalidUnified1,
   274  			expectErr: true,
   275  		},
   276  		{
   277  			name:      "valid unified for cluster with invalid Machine (old top-level items list) and a configmap",
   278  			contents:  invalidUnified2,
   279  			expectErr: true,
   280  		},
   281  		{
   282  			name:      "gibberish in file",
   283  			contents:  `!@#blah ` + validMachines1 + ` blah!@#`,
   284  			expectErr: true,
   285  		},
   286  	}
   287  	for _, testcase := range testcases {
   288  		t.Run(testcase.name, func(*testing.T) {
   289  			file, err := createTempFile(testcase.contents)
   290  			g.Expect(err).ToNot(HaveOccurred())
   291  			defer os.Remove(file)
   292  
   293  			out, err := Parse(ParseInput{File: file})
   294  			if testcase.expectErr {
   295  				g.Expect(err).To(HaveOccurred())
   296  				return
   297  			}
   298  
   299  			g.Expect(err).ToNot(HaveOccurred())
   300  			g.Expect(out.Machines).To(HaveLen(testcase.expectedMachineCount))
   301  		})
   302  	}
   303  }
   304  
   305  func createTempFile(contents string) (filename string, reterr error) {
   306  	f, err := os.CreateTemp("", "")
   307  	if err != nil {
   308  		return "", errors.Wrap(err, "failed to create temporary file")
   309  	}
   310  	defer func() {
   311  		if err := f.Close(); err != nil {
   312  			reterr = kerrors.NewAggregate([]error{reterr, err})
   313  		}
   314  	}()
   315  	_, _ = f.WriteString(contents)
   316  	return f.Name(), nil
   317  }
   318  
   319  func TestToUnstructured(t *testing.T) {
   320  	type args struct {
   321  		rawyaml []byte
   322  	}
   323  	tests := []struct {
   324  		name          string
   325  		args          args
   326  		wantObjsCount int
   327  		wantErr       bool
   328  		err           string
   329  	}{
   330  		{
   331  			name: "single object",
   332  			args: args{
   333  				rawyaml: []byte("apiVersion: v1\n" +
   334  					"kind: ConfigMap\n"),
   335  			},
   336  			wantObjsCount: 1,
   337  			wantErr:       false,
   338  		},
   339  		{
   340  			name: "multiple objects are detected",
   341  			args: args{
   342  				rawyaml: []byte("apiVersion: v1\n" +
   343  					"kind: ConfigMap\n" +
   344  					"---\n" +
   345  					"apiVersion: v1\n" +
   346  					"kind: Secret\n"),
   347  			},
   348  			wantObjsCount: 2,
   349  			wantErr:       false,
   350  		},
   351  		{
   352  			name: "empty object are dropped",
   353  			args: args{
   354  				rawyaml: []byte("---\n" + // empty objects before
   355  					"---\n" +
   356  					"---\n" +
   357  					"apiVersion: v1\n" +
   358  					"kind: ConfigMap\n" +
   359  					"---\n" + // empty objects in the middle
   360  					"---\n" +
   361  					"---\n" +
   362  					"apiVersion: v1\n" +
   363  					"kind: Secret\n" +
   364  					"---\n" + // empty objects after
   365  					"---\n" +
   366  					"---\n"),
   367  			},
   368  			wantObjsCount: 2,
   369  			wantErr:       false,
   370  		},
   371  		{
   372  			name: "--- in the middle of objects are ignored",
   373  			args: args{
   374  				[]byte("apiVersion: v1\n" +
   375  					"kind: ConfigMap\n" +
   376  					"data: \n" +
   377  					" key: |\n" +
   378  					"  ··Several lines of text,\n" +
   379  					"  ··with some --- \n" +
   380  					"  ---\n" +
   381  					"  ··in the middle\n" +
   382  					"---\n" +
   383  					"apiVersion: v1\n" +
   384  					"kind: Secret\n"),
   385  			},
   386  			wantObjsCount: 2,
   387  			wantErr:       false,
   388  		},
   389  		{
   390  			name: "returns error for invalid yaml",
   391  			args: args{
   392  				rawyaml: []byte("apiVersion: v1\n" +
   393  					"kind: ConfigMap\n" +
   394  					"---\n" +
   395  					"apiVersion: v1\n" +
   396  					"foobar\n" +
   397  					"kind: Secret\n"),
   398  			},
   399  			wantErr: true,
   400  			err:     "failed to unmarshal the 2nd yaml document",
   401  		},
   402  		{
   403  			name: "returns error for invalid yaml",
   404  			args: args{
   405  				rawyaml: []byte("apiVersion: v1\n" +
   406  					"kind: ConfigMap\n" +
   407  					"---\n" +
   408  					"apiVersion: v1\n" +
   409  					"kind: Pod\n" +
   410  					"---\n" +
   411  					"apiVersion: v1\n" +
   412  					"kind: Deployment\n" +
   413  					"---\n" +
   414  					"apiVersion: v1\n" +
   415  					"foobar\n" +
   416  					"kind: ConfigMap\n"),
   417  			},
   418  			wantErr: true,
   419  			err:     "failed to unmarshal the 4th yaml document",
   420  		},
   421  		{
   422  			name: "returns error for invalid yaml",
   423  			args: args{
   424  				rawyaml: []byte("apiVersion: v1\n" +
   425  					"foobar\n" +
   426  					"kind: ConfigMap\n" +
   427  					"---\n" +
   428  					"apiVersion: v1\n" +
   429  					"kind: Secret\n"),
   430  			},
   431  			wantErr: true,
   432  			err:     "failed to unmarshal the 1st yaml document",
   433  		},
   434  	}
   435  	for _, tt := range tests {
   436  		t.Run(tt.name, func(t *testing.T) {
   437  			g := NewWithT(t)
   438  
   439  			got, err := ToUnstructured(tt.args.rawyaml)
   440  			if tt.wantErr {
   441  				g.Expect(err).To(HaveOccurred())
   442  				if tt.err != "" {
   443  					g.Expect(err.Error()).To(ContainSubstring(tt.err))
   444  				}
   445  				return
   446  			}
   447  			g.Expect(err).ToNot(HaveOccurred())
   448  			g.Expect(got).To(HaveLen(tt.wantObjsCount))
   449  		})
   450  	}
   451  }
   452  
   453  func TestFromUnstructured(t *testing.T) {
   454  	rawyaml := []byte("apiVersion: v1\n" +
   455  		"kind: ConfigMap")
   456  
   457  	unstructuredObj := unstructured.Unstructured{
   458  		Object: map[string]interface{}{
   459  			"apiVersion": "v1",
   460  			"kind":       "ConfigMap",
   461  		},
   462  	}
   463  
   464  	convertedyaml, err := FromUnstructured([]unstructured.Unstructured{unstructuredObj})
   465  	g := NewWithT(t)
   466  	g.Expect(err).ToNot(HaveOccurred())
   467  	g.Expect(string(rawyaml)).To(Equal(string(convertedyaml)))
   468  }
   469  
   470  func TestRaw(t *testing.T) {
   471  	g := NewWithT(t)
   472  
   473  	input := `
   474  		apiVersion:v1
   475  		kind:newKind
   476  		spec:
   477  			param: abc
   478  	`
   479  	output := "apiVersion:v1\nkind:newKind\nspec:\n\tparam: abc\n"
   480  	result := Raw(input)
   481  	g.Expect(result).To(Equal(output))
   482  }