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