github.com/appscode/helm@v3.0.0-alpha.1+incompatible/pkg/chartutil/values_test.go (about) 1 /* 2 Copyright The Helm 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 chartutil 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "testing" 25 "text/template" 26 27 "helm.sh/helm/pkg/chart" 28 ) 29 30 func TestReadValues(t *testing.T) { 31 doc := `# Test YAML parse 32 poet: "Coleridge" 33 title: "Rime of the Ancient Mariner" 34 stanza: 35 - "at" 36 - "length" 37 - "did" 38 - cross 39 - an 40 - Albatross 41 42 mariner: 43 with: "crossbow" 44 shot: "ALBATROSS" 45 46 water: 47 water: 48 where: "everywhere" 49 nor: "any drop to drink" 50 ` 51 52 data, err := ReadValues([]byte(doc)) 53 if err != nil { 54 t.Fatalf("Error parsing bytes: %s", err) 55 } 56 matchValues(t, data) 57 58 tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} 59 60 for _, tt := range tests { 61 data, err = ReadValues([]byte(tt)) 62 if err != nil { 63 t.Fatalf("Error parsing bytes (%s): %s", tt, err) 64 } 65 if data == nil { 66 t.Errorf(`YAML string "%s" gave a nil map`, tt) 67 } 68 } 69 } 70 71 func TestToRenderValues(t *testing.T) { 72 73 chartValues := map[string]interface{}{ 74 "name": "al Rashid", 75 "where": map[string]interface{}{ 76 "city": "Basrah", 77 "title": "caliph", 78 }, 79 } 80 81 overideValues := map[string]interface{}{ 82 "name": "Haroun", 83 "where": map[string]interface{}{ 84 "city": "Baghdad", 85 "date": "809 CE", 86 }, 87 } 88 89 c := &chart.Chart{ 90 Metadata: &chart.Metadata{Name: "test"}, 91 Templates: []*chart.File{}, 92 Values: chartValues, 93 Files: []*chart.File{ 94 {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, 95 }, 96 } 97 c.AddDependency(&chart.Chart{ 98 Metadata: &chart.Metadata{Name: "where"}, 99 }) 100 101 o := ReleaseOptions{ 102 Name: "Seven Voyages", 103 IsInstall: true, 104 } 105 106 res, err := ToRenderValues(c, overideValues, o, nil) 107 if err != nil { 108 t.Fatal(err) 109 } 110 111 // Ensure that the top-level values are all set. 112 if name := res["Chart"].(*chart.Metadata).Name; name != "test" { 113 t.Errorf("Expected chart name 'test', got %q", name) 114 } 115 relmap := res["Release"].(map[string]interface{}) 116 if name := relmap["Name"]; name.(string) != "Seven Voyages" { 117 t.Errorf("Expected release name 'Seven Voyages', got %q", name) 118 } 119 if relmap["IsUpgrade"].(bool) { 120 t.Error("Expected upgrade to be false.") 121 } 122 if !relmap["IsInstall"].(bool) { 123 t.Errorf("Expected install to be true.") 124 } 125 if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") { 126 t.Error("Expected Capabilities to have v1 as an API") 127 } 128 if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" { 129 t.Error("Expected Capabilities to have a Kube version") 130 } 131 132 vals := res["Values"].(Values) 133 if vals["name"] != "Haroun" { 134 t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) 135 } 136 where := vals["where"].(map[string]interface{}) 137 expects := map[string]string{ 138 "city": "Baghdad", 139 "date": "809 CE", 140 "title": "caliph", 141 } 142 for field, expect := range expects { 143 if got := where[field]; got != expect { 144 t.Errorf("Expected %q, got %q (%v)", expect, got, where) 145 } 146 } 147 } 148 149 func TestReadValuesFile(t *testing.T) { 150 data, err := ReadValuesFile("./testdata/coleridge.yaml") 151 if err != nil { 152 t.Fatalf("Error reading YAML file: %s", err) 153 } 154 matchValues(t, data) 155 } 156 157 func TestValidateAgainstSingleSchema(t *testing.T) { 158 values, err := ReadValuesFile("./testdata/test-values.yaml") 159 if err != nil { 160 t.Fatalf("Error reading YAML file: %s", err) 161 } 162 schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") 163 if err != nil { 164 t.Fatalf("Error reading YAML file: %s", err) 165 } 166 167 if err := ValidateAgainstSingleSchema(values, schema); err != nil { 168 t.Errorf("Error validating Values against Schema: %s", err) 169 } 170 } 171 172 func TestValidateAgainstSingleSchemaNegative(t *testing.T) { 173 values, err := ReadValuesFile("./testdata/test-values-negative.yaml") 174 if err != nil { 175 t.Fatalf("Error reading YAML file: %s", err) 176 } 177 schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") 178 if err != nil { 179 t.Fatalf("Error reading YAML file: %s", err) 180 } 181 182 var errString string 183 if err := ValidateAgainstSingleSchema(values, schema); err == nil { 184 t.Fatalf("Expected an error, but got nil") 185 } else { 186 errString = err.Error() 187 } 188 189 expectedErrString := `- (root): employmentInfo is required 190 - age: Must be greater than or equal to 0/1 191 ` 192 if errString != expectedErrString { 193 t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) 194 } 195 } 196 197 const subchrtSchema = `{ 198 "$schema": "http://json-schema.org/draft-07/schema#", 199 "title": "Values", 200 "type": "object", 201 "properties": { 202 "age": { 203 "description": "Age", 204 "minimum": 0, 205 "type": "integer" 206 } 207 }, 208 "required": [ 209 "age" 210 ] 211 } 212 ` 213 214 func TestValidateAgainstSchema(t *testing.T) { 215 subchrtJSON := []byte(subchrtSchema) 216 subchrt := &chart.Chart{ 217 Metadata: &chart.Metadata{ 218 Name: "subchrt", 219 }, 220 Schema: subchrtJSON, 221 } 222 chrt := &chart.Chart{ 223 Metadata: &chart.Metadata{ 224 Name: "chrt", 225 }, 226 } 227 chrt.AddDependency(subchrt) 228 229 vals := map[string]interface{}{ 230 "name": "John", 231 "subchrt": map[string]interface{}{ 232 "age": 25, 233 }, 234 } 235 236 if err := ValidateAgainstSchema(chrt, vals); err != nil { 237 t.Errorf("Error validating Values against Schema: %s", err) 238 } 239 } 240 241 func TestValidateAgainstSchemaNegative(t *testing.T) { 242 subchrtJSON := []byte(subchrtSchema) 243 subchrt := &chart.Chart{ 244 Metadata: &chart.Metadata{ 245 Name: "subchrt", 246 }, 247 Schema: subchrtJSON, 248 } 249 chrt := &chart.Chart{ 250 Metadata: &chart.Metadata{ 251 Name: "chrt", 252 }, 253 } 254 chrt.AddDependency(subchrt) 255 256 vals := map[string]interface{}{ 257 "name": "John", 258 "subchrt": map[string]interface{}{}, 259 } 260 261 var errString string 262 if err := ValidateAgainstSchema(chrt, vals); err == nil { 263 t.Fatalf("Expected an error, but got nil") 264 } else { 265 errString = err.Error() 266 } 267 268 expectedErrString := `subchrt: 269 - (root): age is required 270 ` 271 if errString != expectedErrString { 272 t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) 273 } 274 } 275 276 func ExampleValues() { 277 doc := ` 278 title: "Moby Dick" 279 chapter: 280 one: 281 title: "Loomings" 282 two: 283 title: "The Carpet-Bag" 284 three: 285 title: "The Spouter Inn" 286 ` 287 d, err := ReadValues([]byte(doc)) 288 if err != nil { 289 panic(err) 290 } 291 ch1, err := d.Table("chapter.one") 292 if err != nil { 293 panic("could not find chapter one") 294 } 295 fmt.Print(ch1["title"]) 296 // Output: 297 // Loomings 298 } 299 300 func TestTable(t *testing.T) { 301 doc := ` 302 title: "Moby Dick" 303 chapter: 304 one: 305 title: "Loomings" 306 two: 307 title: "The Carpet-Bag" 308 three: 309 title: "The Spouter Inn" 310 ` 311 d, err := ReadValues([]byte(doc)) 312 if err != nil { 313 t.Fatalf("Failed to parse the White Whale: %s", err) 314 } 315 316 if _, err := d.Table("title"); err == nil { 317 t.Fatalf("Title is not a table.") 318 } 319 320 if _, err := d.Table("chapter"); err != nil { 321 t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) 322 } 323 324 if v, err := d.Table("chapter.one"); err != nil { 325 t.Errorf("Failed to get chapter.one: %s", err) 326 } else if v["title"] != "Loomings" { 327 t.Errorf("Unexpected title: %s", v["title"]) 328 } 329 330 if _, err := d.Table("chapter.three"); err != nil { 331 t.Errorf("Chapter three is missing: %s\n%v", err, d) 332 } 333 334 if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { 335 t.Errorf("I think you mean 'Epilogue'") 336 } 337 } 338 339 func matchValues(t *testing.T, data map[string]interface{}) { 340 if data["poet"] != "Coleridge" { 341 t.Errorf("Unexpected poet: %s", data["poet"]) 342 } 343 344 if o, err := ttpl("{{len .stanza}}", data); err != nil { 345 t.Errorf("len stanza: %s", err) 346 } else if o != "6" { 347 t.Errorf("Expected 6, got %s", o) 348 } 349 350 if o, err := ttpl("{{.mariner.shot}}", data); err != nil { 351 t.Errorf(".mariner.shot: %s", err) 352 } else if o != "ALBATROSS" { 353 t.Errorf("Expected that mariner shot ALBATROSS") 354 } 355 356 if o, err := ttpl("{{.water.water.where}}", data); err != nil { 357 t.Errorf(".water.water.where: %s", err) 358 } else if o != "everywhere" { 359 t.Errorf("Expected water water everywhere") 360 } 361 } 362 363 func ttpl(tpl string, v map[string]interface{}) (string, error) { 364 var b bytes.Buffer 365 tt := template.Must(template.New("t").Parse(tpl)) 366 err := tt.Execute(&b, v) 367 return b.String(), err 368 } 369 370 // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 371 var testCoalesceValuesYaml = []byte(` 372 top: yup 373 bottom: null 374 right: Null 375 left: NULL 376 front: ~ 377 back: "" 378 379 global: 380 name: Ishmael 381 subject: Queequeg 382 nested: 383 boat: true 384 385 pequod: 386 global: 387 name: Stinky 388 harpooner: Tashtego 389 nested: 390 boat: false 391 sail: true 392 ahab: 393 scope: whale 394 `) 395 396 func TestCoalesceValues(t *testing.T) { 397 c := loadChart(t, "testdata/moby") 398 399 vals, err := ReadValues(testCoalesceValuesYaml) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 v, err := CoalesceValues(c, vals) 405 if err != nil { 406 t.Fatal(err) 407 } 408 j, _ := json.MarshalIndent(v, "", " ") 409 t.Logf("Coalesced Values: %s", string(j)) 410 411 tests := []struct { 412 tpl string 413 expect string 414 }{ 415 {"{{.top}}", "yup"}, 416 {"{{.back}}", ""}, 417 {"{{.name}}", "moby"}, 418 {"{{.global.name}}", "Ishmael"}, 419 {"{{.global.subject}}", "Queequeg"}, 420 {"{{.global.harpooner}}", "<no value>"}, 421 {"{{.pequod.name}}", "pequod"}, 422 {"{{.pequod.ahab.name}}", "ahab"}, 423 {"{{.pequod.ahab.scope}}", "whale"}, 424 {"{{.pequod.ahab.global.name}}", "Ishmael"}, 425 {"{{.pequod.ahab.global.subject}}", "Queequeg"}, 426 {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, 427 {"{{.pequod.global.name}}", "Ishmael"}, 428 {"{{.pequod.global.subject}}", "Queequeg"}, 429 {"{{.spouter.global.name}}", "Ishmael"}, 430 {"{{.spouter.global.harpooner}}", "<no value>"}, 431 432 {"{{.global.nested.boat}}", "true"}, 433 {"{{.pequod.global.nested.boat}}", "true"}, 434 {"{{.spouter.global.nested.boat}}", "true"}, 435 {"{{.pequod.global.nested.sail}}", "true"}, 436 {"{{.spouter.global.nested.sail}}", "<no value>"}, 437 } 438 439 for _, tt := range tests { 440 if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { 441 t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) 442 } 443 } 444 445 nullKeys := []string{"bottom", "right", "left", "front"} 446 for _, nullKey := range nullKeys { 447 if _, ok := v[nullKey]; ok { 448 t.Errorf("Expected key %q to be removed, still present", nullKey) 449 } 450 } 451 } 452 453 func TestCoalesceTables(t *testing.T) { 454 dst := map[string]interface{}{ 455 "name": "Ishmael", 456 "address": map[string]interface{}{ 457 "street": "123 Spouter Inn Ct.", 458 "city": "Nantucket", 459 }, 460 "details": map[string]interface{}{ 461 "friends": []string{"Tashtego"}, 462 }, 463 "boat": "pequod", 464 } 465 src := map[string]interface{}{ 466 "occupation": "whaler", 467 "address": map[string]interface{}{ 468 "state": "MA", 469 "street": "234 Spouter Inn Ct.", 470 }, 471 "details": "empty", 472 "boat": map[string]interface{}{ 473 "mast": true, 474 }, 475 } 476 477 // What we expect is that anything in dst overrides anything in src, but that 478 // otherwise the values are coalesced. 479 CoalesceTables(dst, src) 480 481 if dst["name"] != "Ishmael" { 482 t.Errorf("Unexpected name: %s", dst["name"]) 483 } 484 if dst["occupation"] != "whaler" { 485 t.Errorf("Unexpected occupation: %s", dst["occupation"]) 486 } 487 488 addr, ok := dst["address"].(map[string]interface{}) 489 if !ok { 490 t.Fatal("Address went away.") 491 } 492 493 if addr["street"].(string) != "123 Spouter Inn Ct." { 494 t.Errorf("Unexpected address: %v", addr["street"]) 495 } 496 497 if addr["city"].(string) != "Nantucket" { 498 t.Errorf("Unexpected city: %v", addr["city"]) 499 } 500 501 if addr["state"].(string) != "MA" { 502 t.Errorf("Unexpected state: %v", addr["state"]) 503 } 504 505 if det, ok := dst["details"].(map[string]interface{}); !ok { 506 t.Fatalf("Details is the wrong type: %v", dst["details"]) 507 } else if _, ok := det["friends"]; !ok { 508 t.Error("Could not find your friends. Maybe you don't have any. :-(") 509 } 510 511 if dst["boat"].(string) != "pequod" { 512 t.Errorf("Expected boat string, got %v", dst["boat"]) 513 } 514 } 515 516 func TestPathValue(t *testing.T) { 517 doc := ` 518 title: "Moby Dick" 519 chapter: 520 one: 521 title: "Loomings" 522 two: 523 title: "The Carpet-Bag" 524 three: 525 title: "The Spouter Inn" 526 ` 527 d, err := ReadValues([]byte(doc)) 528 if err != nil { 529 t.Fatalf("Failed to parse the White Whale: %s", err) 530 } 531 532 if v, err := d.PathValue("chapter.one.title"); err != nil { 533 t.Errorf("Got error instead of title: %s\n%v", err, d) 534 } else if v != "Loomings" { 535 t.Errorf("No error but got wrong value for title: %s\n%v", err, d) 536 } 537 if _, err := d.PathValue("chapter.one.doesntexist"); err == nil { 538 t.Errorf("Non-existent key should return error: %s\n%v", err, d) 539 } 540 if _, err := d.PathValue("chapter.doesntexist.one"); err == nil { 541 t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) 542 } 543 if _, err := d.PathValue(""); err == nil { 544 t.Error("Asking for the value from an empty path should yield an error") 545 } 546 if v, err := d.PathValue("title"); err == nil { 547 if v != "Moby Dick" { 548 t.Errorf("Failed to return values for root key title") 549 } 550 } 551 }