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 }