github.com/latiif/helm@v2.15.0+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 "reflect" 24 "strings" 25 "testing" 26 "text/template" 27 28 "github.com/golang/protobuf/ptypes/any" 29 30 kversion "k8s.io/apimachinery/pkg/version" 31 "k8s.io/helm/pkg/proto/hapi/chart" 32 "k8s.io/helm/pkg/timeconv" 33 "k8s.io/helm/pkg/version" 34 ) 35 36 func TestReadValues(t *testing.T) { 37 doc := `# Test YAML parse 38 poet: "Coleridge" 39 title: "Rime of the Ancient Mariner" 40 stanza: 41 - "at" 42 - "length" 43 - "did" 44 - cross 45 - an 46 - Albatross 47 48 mariner: 49 with: "crossbow" 50 shot: "ALBATROSS" 51 52 water: 53 water: 54 where: "everywhere" 55 nor: "any drop to drink" 56 temperature: 1234567890 57 ` 58 59 data, err := ReadValues([]byte(doc)) 60 if err != nil { 61 t.Fatalf("Error parsing bytes: %s", err) 62 } 63 matchValues(t, data) 64 65 tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} 66 67 for _, tt := range tests { 68 data, err = ReadValues([]byte(tt)) 69 if err != nil { 70 t.Fatalf("Error parsing bytes (%s): %s", tt, err) 71 } 72 if data == nil { 73 t.Errorf(`YAML string "%s" gave a nil map`, tt) 74 } 75 } 76 } 77 78 func TestToRenderValuesCaps(t *testing.T) { 79 80 chartValues := ` 81 name: al Rashid 82 where: 83 city: Basrah 84 title: caliph 85 ` 86 overideValues := ` 87 name: Haroun 88 where: 89 city: Baghdad 90 date: 809 CE 91 ` 92 93 c := &chart.Chart{ 94 Metadata: &chart.Metadata{Name: "test"}, 95 Templates: []*chart.Template{}, 96 Values: &chart.Config{Raw: chartValues}, 97 Dependencies: []*chart.Chart{ 98 { 99 Metadata: &chart.Metadata{Name: "where"}, 100 Values: &chart.Config{Raw: ""}, 101 }, 102 }, 103 Files: []*any.Any{ 104 {TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")}, 105 }, 106 } 107 v := &chart.Config{Raw: overideValues} 108 109 o := ReleaseOptions{ 110 Name: "Seven Voyages", 111 Time: timeconv.Now(), 112 Namespace: "al Basrah", 113 IsInstall: true, 114 Revision: 5, 115 } 116 117 caps := &Capabilities{ 118 APIVersions: DefaultVersionSet, 119 TillerVersion: version.GetVersionProto(), 120 KubeVersion: &kversion.Info{Major: "1"}, 121 } 122 123 res, err := ToRenderValuesCaps(c, v, o, caps) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 // Ensure that the top-level values are all set. 129 if name := res["Chart"].(*chart.Metadata).Name; name != "test" { 130 t.Errorf("Expected chart name 'test', got %q", name) 131 } 132 relmap := res["Release"].(map[string]interface{}) 133 if name := relmap["Name"]; name.(string) != "Seven Voyages" { 134 t.Errorf("Expected release name 'Seven Voyages', got %q", name) 135 } 136 if rev := relmap["Revision"]; rev.(int) != 5 { 137 t.Errorf("Expected release revision %d, got %q", 5, rev) 138 } 139 if relmap["IsUpgrade"].(bool) { 140 t.Error("Expected upgrade to be false.") 141 } 142 if !relmap["IsInstall"].(bool) { 143 t.Errorf("Expected install to be true.") 144 } 145 if data := res["Files"].(Files)["scheherazade/shahryar.txt"]; string(data) != "1,001 Nights" { 146 t.Errorf("Expected file '1,001 Nights', got %q", string(data)) 147 } 148 if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") { 149 t.Error("Expected Capabilities to have v1 as an API") 150 } 151 if res["Capabilities"].(*Capabilities).TillerVersion.SemVer == "" { 152 t.Error("Expected Capabilities to have a Tiller version") 153 } 154 if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" { 155 t.Error("Expected Capabilities to have a Kube version") 156 } 157 158 var vals Values 159 vals = res["Values"].(Values) 160 161 if vals["name"] != "Haroun" { 162 t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) 163 } 164 where := vals["where"].(map[string]interface{}) 165 expects := map[string]string{ 166 "city": "Baghdad", 167 "date": "809 CE", 168 "title": "caliph", 169 } 170 for field, expect := range expects { 171 if got := where[field]; got != expect { 172 t.Errorf("Expected %q, got %q (%v)", expect, got, where) 173 } 174 } 175 } 176 177 func TestReadValuesFile(t *testing.T) { 178 data, err := ReadValuesFile("./testdata/coleridge.yaml") 179 if err != nil { 180 t.Fatalf("Error reading YAML file: %s", err) 181 } 182 matchValues(t, data) 183 } 184 185 func ExampleValues() { 186 doc := ` 187 title: "Moby Dick" 188 chapter: 189 one: 190 title: "Loomings" 191 two: 192 title: "The Carpet-Bag" 193 three: 194 title: "The Spouter Inn" 195 ` 196 d, err := ReadValues([]byte(doc)) 197 if err != nil { 198 panic(err) 199 } 200 ch1, err := d.Table("chapter.one") 201 if err != nil { 202 panic("could not find chapter one") 203 } 204 fmt.Print(ch1["title"]) 205 // Output: 206 // Loomings 207 } 208 209 func TestTable(t *testing.T) { 210 doc := ` 211 title: "Moby Dick" 212 chapter: 213 one: 214 title: "Loomings" 215 two: 216 title: "The Carpet-Bag" 217 three: 218 title: "The Spouter Inn" 219 ` 220 d, err := ReadValues([]byte(doc)) 221 if err != nil { 222 t.Fatalf("Failed to parse the White Whale: %s", err) 223 } 224 225 if _, err := d.Table("title"); err == nil { 226 t.Fatalf("Title is not a table.") 227 } 228 229 if _, err := d.Table("chapter"); err != nil { 230 t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) 231 } 232 233 if v, err := d.Table("chapter.one"); err != nil { 234 t.Errorf("Failed to get chapter.one: %s", err) 235 } else if v["title"] != "Loomings" { 236 t.Errorf("Unexpected title: %s", v["title"]) 237 } 238 239 if _, err := d.Table("chapter.three"); err != nil { 240 t.Errorf("Chapter three is missing: %s\n%v", err, d) 241 } 242 243 if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { 244 t.Errorf("I think you mean 'Epilogue'") 245 } 246 } 247 248 func matchValues(t *testing.T, data map[string]interface{}) { 249 if data["poet"] != "Coleridge" { 250 t.Errorf("Unexpected poet: %s", data["poet"]) 251 } 252 253 if o, err := ttpl("{{len .stanza}}", data); err != nil { 254 t.Errorf("len stanza: %s", err) 255 } else if o != "6" { 256 t.Errorf("Expected 6, got %s", o) 257 } 258 259 if o, err := ttpl("{{.mariner.shot}}", data); err != nil { 260 t.Errorf(".mariner.shot: %s", err) 261 } else if o != "ALBATROSS" { 262 t.Errorf("Expected that mariner shot ALBATROSS") 263 } 264 265 if o, err := ttpl("{{.water.water.where}}", data); err != nil { 266 t.Errorf(".water.water.where: %s", err) 267 } else if o != "everywhere" { 268 t.Errorf("Expected water water everywhere") 269 } 270 271 if o, err := ttpl("{{.water.water.temperature}}", data); err != nil { 272 t.Errorf(".water.water.temperature: %s", err) 273 } else if o != "1234567890" { 274 t.Errorf("Expected water water temperature: 1234567890, got: %s", o) 275 } 276 } 277 278 func ttpl(tpl string, v map[string]interface{}) (string, error) { 279 var b bytes.Buffer 280 tt := template.Must(template.New("t").Parse(tpl)) 281 if err := tt.Execute(&b, v); err != nil { 282 return "", err 283 } 284 return b.String(), nil 285 } 286 287 // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 288 var testCoalesceValuesYaml = ` 289 top: yup 290 bottom: null 291 right: Null 292 left: NULL 293 front: ~ 294 back: "" 295 296 global: 297 name: Ishmael 298 subject: Queequeg 299 nested: 300 boat: true 301 302 pequod: 303 global: 304 name: Stinky 305 harpooner: Tashtego 306 nested: 307 boat: false 308 sail: true 309 ahab: 310 scope: whale 311 312 # test coalesce with nested null values 313 web: 314 livenessProbe: 315 httpGet: null 316 exec: 317 command: 318 - curl 319 - -f 320 - http://localhost:8080/api/v1/info 321 timeoutSeconds: null 322 readinessProbe: 323 httpGet: null 324 exec: 325 command: 326 - curl 327 - -f 328 - http://localhost:8080/api/v1/info 329 timeoutSeconds: null # catches the case where this wasn't defined in the original source... 330 ` 331 332 func TestCoalesceValues(t *testing.T) { 333 tchart := "testdata/moby" 334 c, err := LoadDir(tchart) 335 if err != nil { 336 t.Fatal(err) 337 } 338 339 tvals := &chart.Config{Raw: testCoalesceValuesYaml} 340 341 v, err := CoalesceValues(c, tvals) 342 if err != nil { 343 t.Fatal(err) 344 } 345 j, _ := json.MarshalIndent(v, "", " ") 346 t.Logf("Coalesced Values: %s", string(j)) 347 348 tests := []struct { 349 tpl string 350 expect string 351 }{ 352 {"{{.top}}", "yup"}, 353 {"{{.back}}", ""}, 354 {"{{.name}}", "moby"}, 355 {"{{.global.name}}", "Ishmael"}, 356 {"{{.global.subject}}", "Queequeg"}, 357 {"{{.global.harpooner}}", "<no value>"}, 358 {"{{.pequod.name}}", "pequod"}, 359 {"{{.pequod.ahab.name}}", "ahab"}, 360 {"{{.pequod.ahab.scope}}", "whale"}, 361 {"{{.pequod.ahab.global.name}}", "Ishmael"}, 362 {"{{.pequod.ahab.global.subject}}", "Queequeg"}, 363 {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, 364 {"{{.pequod.global.name}}", "Ishmael"}, 365 {"{{.pequod.global.subject}}", "Queequeg"}, 366 {"{{.spouter.global.name}}", "Ishmael"}, 367 {"{{.spouter.global.harpooner}}", "<no value>"}, 368 369 {"{{.global.nested.boat}}", "true"}, 370 {"{{.pequod.global.nested.boat}}", "true"}, 371 {"{{.spouter.global.nested.boat}}", "true"}, 372 {"{{.pequod.global.nested.sail}}", "true"}, 373 {"{{.spouter.global.nested.sail}}", "<no value>"}, 374 375 {"{{.web.livenessProbe.failureThreshold}}", "5"}, 376 {"{{.web.livenessProbe.initialDelaySeconds}}", "10"}, 377 {"{{.web.livenessProbe.periodSeconds}}", "15"}, 378 {"{{.web.livenessProbe.exec}}", "map[command:[curl -f http://localhost:8080/api/v1/info]]"}, 379 380 {"{{.web.readinessProbe.exec}}", "map[command:[curl -f http://localhost:8080/api/v1/info]]"}, 381 } 382 383 for _, tt := range tests { 384 if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { 385 t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) 386 } 387 } 388 389 nullKeys := []string{"bottom", "right", "left", "front", 390 "web.livenessProbe.httpGet", "web.readinessProbe.httpGet", "web.livenessProbe.timeoutSeconds", "web.readinessProbe.timeoutSeconds"} 391 for _, nullKey := range nullKeys { 392 parts := strings.Split(nullKey, ".") 393 curMap := v 394 for partIdx, part := range parts { 395 nextVal, ok := curMap[part] 396 if partIdx == len(parts)-1 { // are we the last? 397 if ok { 398 t.Errorf("Expected key %q to be removed, still present", nullKey) 399 break 400 } 401 } else { // we are not the last 402 if !ok { 403 t.Errorf("Expected key %q to be removed, but partial parent path was not found", nullKey) 404 break 405 } 406 curMap, ok = nextVal.(map[string]interface{}) 407 if !ok { 408 t.Errorf("Expected key %q to be removed, but partial parent path did not result in a map", nullKey) 409 break 410 } 411 } 412 } 413 } 414 } 415 416 func TestCoalesceTables(t *testing.T) { 417 dst := map[string]interface{}{ 418 "name": "Ishmael", 419 "address": map[string]interface{}{ 420 "street": "123 Spouter Inn Ct.", 421 "city": "Nantucket", 422 }, 423 "details": map[string]interface{}{ 424 "friends": []string{"Tashtego"}, 425 }, 426 "boat": "pequod", 427 } 428 src := map[string]interface{}{ 429 "occupation": "whaler", 430 "address": map[string]interface{}{ 431 "state": "MA", 432 "street": "234 Spouter Inn Ct.", 433 }, 434 "details": "empty", 435 "boat": map[string]interface{}{ 436 "mast": true, 437 }, 438 } 439 440 // What we expect is that anything in dst overrides anything in src, but that 441 // otherwise the values are coalesced. 442 dst = coalesceTables(dst, src, "") 443 444 if dst["name"] != "Ishmael" { 445 t.Errorf("Unexpected name: %s", dst["name"]) 446 } 447 if dst["occupation"] != "whaler" { 448 t.Errorf("Unexpected occupation: %s", dst["occupation"]) 449 } 450 451 addr, ok := dst["address"].(map[string]interface{}) 452 if !ok { 453 t.Fatal("Address went away.") 454 } 455 456 if addr["street"].(string) != "123 Spouter Inn Ct." { 457 t.Errorf("Unexpected address: %v", addr["street"]) 458 } 459 460 if addr["city"].(string) != "Nantucket" { 461 t.Errorf("Unexpected city: %v", addr["city"]) 462 } 463 464 if addr["state"].(string) != "MA" { 465 t.Errorf("Unexpected state: %v", addr["state"]) 466 } 467 468 if det, ok := dst["details"].(map[string]interface{}); !ok { 469 t.Fatalf("Details is the wrong type: %v", dst["details"]) 470 } else if _, ok := det["friends"]; !ok { 471 t.Error("Could not find your friends. Maybe you don't have any. :-(") 472 } 473 474 if dst["boat"].(string) != "pequod" { 475 t.Errorf("Expected boat string, got %v", dst["boat"]) 476 } 477 } 478 479 func TestCoalesceSubchart(t *testing.T) { 480 tchart := "testdata/moby" 481 c, err := LoadDir(tchart) 482 if err != nil { 483 t.Fatal(err) 484 } 485 486 tvals := &chart.Config{} 487 488 v, err := CoalesceValues(c, tvals) 489 if err != nil { 490 t.Fatal(err) 491 } 492 j, _ := json.MarshalIndent(v, "", " ") 493 t.Logf("Coalesced Values: %s", string(j)) 494 495 subchartValues, ok := v["spouter"].(map[string]interface{}) 496 if !ok { 497 t.Errorf("Subchart values not found") 498 } 499 500 if _, ok := subchartValues["foo"]; ok { 501 t.Errorf("Expected key foo to be removed, still present") 502 } 503 } 504 505 func TestPathValue(t *testing.T) { 506 doc := ` 507 title: "Moby Dick" 508 chapter: 509 one: 510 title: "Loomings" 511 two: 512 title: "The Carpet-Bag" 513 three: 514 title: "The Spouter Inn" 515 ` 516 d, err := ReadValues([]byte(doc)) 517 if err != nil { 518 t.Fatalf("Failed to parse the White Whale: %s", err) 519 } 520 521 if v, err := d.PathValue("chapter.one.title"); err != nil { 522 t.Errorf("Got error instead of title: %s\n%v", err, d) 523 } else if v != "Loomings" { 524 t.Errorf("No error but got wrong value for title: %s\n%v", err, d) 525 } 526 if _, err := d.PathValue("chapter.one.doesntexist"); err == nil { 527 t.Errorf("Non-existent key should return error: %s\n%v", err, d) 528 } 529 if _, err := d.PathValue("chapter.doesntexist.one"); err == nil { 530 t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) 531 } 532 if _, err := d.PathValue(""); err == nil { 533 t.Error("Asking for the value from an empty path should yield an error") 534 } 535 if v, err := d.PathValue("title"); err == nil { 536 if v != "Moby Dick" { 537 t.Errorf("Failed to return values for root key title") 538 } 539 } 540 } 541 542 func TestValuesMergeInto(t *testing.T) { 543 testCases := map[string]struct { 544 destination string 545 source string 546 result string 547 }{ 548 "maps are merged": { 549 ` 550 resources: 551 requests: 552 cpu: 400m 553 something: else 554 `, 555 ` 556 resources: 557 requests: 558 cpu: 500m 559 `, 560 ` 561 resources: 562 requests: 563 cpu: 500m 564 something: else 565 `, 566 }, 567 "values are replaced": { 568 ` 569 firstKey: firstValue 570 secondKey: secondValue 571 thirdKey: thirdValue 572 `, 573 ` 574 firstKey: newFirstValue 575 thirdKey: newThirdValue 576 `, 577 ` 578 firstKey: newFirstValue 579 secondKey: secondValue 580 thirdKey: newThirdValue 581 `, 582 }, 583 "new values are added": { 584 ` 585 existingKey: existingValue 586 `, 587 ` 588 newKey: newValue 589 anotherNewKey: 590 nestedNewKey: nestedNewValue 591 `, 592 ` 593 existingKey: existingValue 594 newKey: newValue 595 anotherNewKey: 596 nestedNewKey: nestedNewValue 597 `, 598 }, 599 } 600 601 for name, tc := range testCases { 602 d, err := ReadValues([]byte(tc.destination)) 603 if err != nil { 604 t.Error(err) 605 } 606 s, err := ReadValues([]byte(tc.source)) 607 if err != nil { 608 t.Error(err) 609 } 610 expectedRes, err := ReadValues([]byte(tc.result)) 611 if err != nil { 612 t.Error(err) 613 } 614 615 d.MergeInto(s) 616 617 if !reflect.DeepEqual(expectedRes, d) { 618 t.Errorf("%s: Expected %v, but got %v", name, expectedRes, d) 619 } 620 } 621 } 622 623 func TestOverriteTableItemWithNonTableValue(t *testing.T) { 624 // src has a table value for "foo" 625 src := map[string]interface{}{ 626 "foo": map[string]interface{}{ 627 "baz": "boz", 628 }, 629 } 630 631 // dst has a non-table value for "foo" 632 dst := map[string]interface{}{ 633 "foo": "bar", 634 } 635 636 // result - this may print a warning, but we has always "worked" 637 result := coalesceTables(dst, src, "") 638 expected := map[string]interface{}{ 639 "foo": "bar", 640 } 641 642 if !reflect.DeepEqual(result, expected) { 643 t.Errorf("Expected %v, but got %v", expected, result) 644 } 645 } 646 647 func TestSubchartCoaleseWithNullValue(t *testing.T) { 648 v, err := CoalesceValues(&chart.Chart{ 649 Metadata: &chart.Metadata{Name: "demo"}, 650 Dependencies: []*chart.Chart{ 651 { 652 Metadata: &chart.Metadata{Name: "logstash"}, 653 Values: &chart.Config{ 654 Raw: `livenessProbe: {httpGet: {path: "/", port: monitor}}`, 655 }, 656 }, 657 }, 658 Values: &chart.Config{ 659 Raw: `logstash: {livenessProbe: {httpGet: null, exec: "/bin/true"}}`, 660 }, 661 }, &chart.Config{}) 662 if err != nil { 663 t.Errorf("Failed with %s", err) 664 } 665 result := v.AsMap() 666 expected := map[string]interface{}{ 667 "logstash": map[string]interface{}{ 668 "global": map[string]interface{}{}, 669 "livenessProbe": map[string]interface{}{ 670 "exec": "/bin/true", 671 }, 672 }, 673 } 674 if !reflect.DeepEqual(result, expected) { 675 t.Errorf("got %+v, expected %+v", result, expected) 676 } 677 }