github.com/koderover/helm@v2.17.0+incompatible/pkg/engine/engine_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 engine 18 19 import ( 20 "fmt" 21 "strings" 22 "sync" 23 "testing" 24 25 "k8s.io/helm/pkg/chartutil" 26 "k8s.io/helm/pkg/proto/hapi/chart" 27 28 "github.com/golang/protobuf/ptypes/any" 29 ) 30 31 func TestSortTemplates(t *testing.T) { 32 tpls := map[string]renderable{ 33 "/mychart/templates/foo.tpl": {}, 34 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {}, 35 "/mychart/templates/bar.tpl": {}, 36 "/mychart/templates/charts/foo/templates/bar.tpl": {}, 37 "/mychart/templates/_foo.tpl": {}, 38 "/mychart/templates/charts/foo/templates/foo.tpl": {}, 39 "/mychart/templates/charts/bar/templates/foo.tpl": {}, 40 } 41 got := sortTemplates(tpls) 42 if len(got) != len(tpls) { 43 t.Fatal("Sorted results are missing templates") 44 } 45 46 expect := []string{ 47 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl", 48 "/mychart/templates/charts/foo/templates/foo.tpl", 49 "/mychart/templates/charts/foo/templates/bar.tpl", 50 "/mychart/templates/charts/bar/templates/foo.tpl", 51 "/mychart/templates/foo.tpl", 52 "/mychart/templates/bar.tpl", 53 "/mychart/templates/_foo.tpl", 54 } 55 for i, e := range expect { 56 if got[i] != e { 57 t.Errorf("expected %q, got %q at index %d\n\tExp: %#v\n\tGot: %#v", e, got[i], i, expect, got) 58 } 59 } 60 } 61 62 func TestEngine(t *testing.T) { 63 e := New() 64 65 // Forbidden because they allow access to the host OS. 66 forbidden := []string{"env", "expandenv"} 67 for _, f := range forbidden { 68 if _, ok := e.FuncMap[f]; ok { 69 t.Errorf("Forbidden function %s exists in FuncMap.", f) 70 } 71 } 72 } 73 74 func TestFuncMap(t *testing.T) { 75 fns := FuncMap() 76 forbidden := []string{"env", "expandenv"} 77 for _, f := range forbidden { 78 if _, ok := fns[f]; ok { 79 t.Errorf("Forbidden function %s exists in FuncMap.", f) 80 } 81 } 82 83 // Test for Engine-specific template functions. 84 expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"} 85 for _, f := range expect { 86 if _, ok := fns[f]; !ok { 87 t.Errorf("Expected add-on function %q", f) 88 } 89 } 90 } 91 92 func TestRender(t *testing.T) { 93 c := &chart.Chart{ 94 Metadata: &chart.Metadata{ 95 Name: "moby", 96 Version: "1.2.3", 97 }, 98 Templates: []*chart.Template{ 99 {Name: "templates/test1", Data: []byte("{{.outer | title }} {{.inner | title}}")}, 100 {Name: "templates/test2", Data: []byte("{{.global.callme | lower }}")}, 101 {Name: "templates/test3", Data: []byte("{{.noValue}}")}, 102 }, 103 Values: &chart.Config{ 104 Raw: "outer: DEFAULT\ninner: DEFAULT", 105 }, 106 } 107 108 vals := &chart.Config{ 109 Raw: ` 110 outer: spouter 111 inner: inn 112 global: 113 callme: Ishmael 114 `} 115 116 e := New() 117 v, err := chartutil.CoalesceValues(c, vals) 118 if err != nil { 119 t.Fatalf("Failed to coalesce values: %s", err) 120 } 121 out, err := e.Render(c, v) 122 if err != nil { 123 t.Errorf("Failed to render templates: %s", err) 124 } 125 126 expect := "Spouter Inn" 127 if out["moby/templates/test1"] != expect { 128 t.Errorf("Expected %q, got %q", expect, out["test1"]) 129 } 130 131 expect = "ishmael" 132 if out["moby/templates/test2"] != expect { 133 t.Errorf("Expected %q, got %q", expect, out["test2"]) 134 } 135 expect = "" 136 if out["moby/templates/test3"] != expect { 137 t.Errorf("Expected %q, got %q", expect, out["test3"]) 138 } 139 140 if _, err := e.Render(c, v); err != nil { 141 t.Errorf("Unexpected error: %s", err) 142 } 143 } 144 145 func TestRenderInternals(t *testing.T) { 146 // Test the internals of the rendering tool. 147 e := New() 148 149 vals := chartutil.Values{"Name": "one", "Value": "two"} 150 tpls := map[string]renderable{ 151 "one": {tpl: `Hello {{title .Name}}`, vals: vals}, 152 "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals}, 153 // Test whether a template can reliably reference another template 154 // without regard for ordering. 155 "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, 156 } 157 158 out, err := e.render(tpls) 159 if err != nil { 160 t.Fatalf("Failed template rendering: %s", err) 161 } 162 163 if len(out) != 3 { 164 t.Fatalf("Expected 3 templates, got %d", len(out)) 165 } 166 167 if out["one"] != "Hello One" { 168 t.Errorf("Expected 'Hello One', got %q", out["one"]) 169 } 170 171 if out["two"] != "Goodbye TWO" { 172 t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) 173 } 174 175 if out["three"] != "Goodbye THREE" { 176 t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) 177 } 178 } 179 180 func TestParallelRenderInternals(t *testing.T) { 181 // Make sure that we can use one Engine to run parallel template renders. 182 e := New() 183 var wg sync.WaitGroup 184 for i := 0; i < 20; i++ { 185 wg.Add(1) 186 go func(i int) { 187 fname := "my/file/name" 188 tt := fmt.Sprintf("expect-%d", i) 189 v := chartutil.Values{"val": tt} 190 tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} 191 out, err := e.render(tpls) 192 if err != nil { 193 t.Errorf("Failed to render %s: %s", tt, err) 194 } 195 if out[fname] != tt { 196 t.Errorf("Expected %q, got %q", tt, out[fname]) 197 } 198 wg.Done() 199 }(i) 200 } 201 wg.Wait() 202 } 203 204 func TestAllTemplates(t *testing.T) { 205 ch1 := &chart.Chart{ 206 Metadata: &chart.Metadata{Name: "ch1"}, 207 Templates: []*chart.Template{ 208 {Name: "templates/foo", Data: []byte("foo")}, 209 {Name: "templates/bar", Data: []byte("bar")}, 210 }, 211 Dependencies: []*chart.Chart{ 212 { 213 Metadata: &chart.Metadata{Name: "laboratory mice"}, 214 Templates: []*chart.Template{ 215 {Name: "templates/pinky", Data: []byte("pinky")}, 216 {Name: "templates/brain", Data: []byte("brain")}, 217 }, 218 Dependencies: []*chart.Chart{{ 219 Metadata: &chart.Metadata{Name: "same thing we do every night"}, 220 Templates: []*chart.Template{ 221 {Name: "templates/innermost", Data: []byte("innermost")}, 222 }}, 223 }, 224 }, 225 }, 226 } 227 228 var v chartutil.Values 229 tpls := allTemplates(ch1, v) 230 if len(tpls) != 5 { 231 t.Errorf("Expected 5 charts, got %d", len(tpls)) 232 } 233 } 234 235 func TestRenderDependency(t *testing.T) { 236 e := New() 237 deptpl := `{{define "myblock"}}World{{end}}` 238 toptpl := `Hello {{template "myblock"}}` 239 ch := &chart.Chart{ 240 Metadata: &chart.Metadata{Name: "outerchart"}, 241 Templates: []*chart.Template{ 242 {Name: "templates/outer", Data: []byte(toptpl)}, 243 }, 244 Dependencies: []*chart.Chart{ 245 { 246 Metadata: &chart.Metadata{Name: "innerchart"}, 247 Templates: []*chart.Template{ 248 {Name: "templates/inner", Data: []byte(deptpl)}, 249 }, 250 }, 251 }, 252 } 253 254 out, err := e.Render(ch, map[string]interface{}{}) 255 256 if err != nil { 257 t.Fatalf("failed to render chart: %s", err) 258 } 259 260 if len(out) != 2 { 261 t.Errorf("Expected 2, got %d", len(out)) 262 } 263 264 expect := "Hello World" 265 if out["outerchart/templates/outer"] != expect { 266 t.Errorf("Expected %q, got %q", expect, out["outer"]) 267 } 268 269 } 270 271 func TestRenderNestedValues(t *testing.T) { 272 e := New() 273 274 innerpath := "templates/inner.tpl" 275 outerpath := "templates/outer.tpl" 276 // Ensure namespacing rules are working. 277 deepestpath := "templates/inner.tpl" 278 checkrelease := "templates/release.tpl" 279 280 deepest := &chart.Chart{ 281 Metadata: &chart.Metadata{Name: "deepest"}, 282 Templates: []*chart.Template{ 283 {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, 284 {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, 285 }, 286 Values: &chart.Config{Raw: `what: "milkshake"`}, 287 } 288 289 inner := &chart.Chart{ 290 Metadata: &chart.Metadata{Name: "herrick"}, 291 Templates: []*chart.Template{ 292 {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, 293 }, 294 Values: &chart.Config{Raw: `who: "Robert"`}, 295 Dependencies: []*chart.Chart{deepest}, 296 } 297 298 outer := &chart.Chart{ 299 Metadata: &chart.Metadata{Name: "top"}, 300 Templates: []*chart.Template{ 301 {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, 302 }, 303 Values: &chart.Config{ 304 Raw: ` 305 what: stinkweed 306 who: me 307 herrick: 308 who: time`, 309 }, 310 Dependencies: []*chart.Chart{inner}, 311 } 312 313 injValues := chart.Config{ 314 Raw: ` 315 what: rosebuds 316 herrick: 317 deepest: 318 what: flower 319 global: 320 when: to-day`, 321 } 322 323 tmp, err := chartutil.CoalesceValues(outer, &injValues) 324 if err != nil { 325 t.Fatalf("Failed to coalesce values: %s", err) 326 } 327 328 inject := chartutil.Values{ 329 "Values": tmp, 330 "Chart": outer.Metadata, 331 "Release": chartutil.Values{ 332 "Name": "dyin", 333 }, 334 } 335 336 t.Logf("Calculated values: %v", inject) 337 338 out, err := e.Render(outer, inject) 339 if err != nil { 340 t.Fatalf("failed to render templates: %s", err) 341 } 342 343 fullouterpath := "top/" + outerpath 344 if out[fullouterpath] != "Gather ye rosebuds while ye may" { 345 t.Errorf("Unexpected outer: %q", out[fullouterpath]) 346 } 347 348 fullinnerpath := "top/charts/herrick/" + innerpath 349 if out[fullinnerpath] != "Old time is still a-flyin'" { 350 t.Errorf("Unexpected inner: %q", out[fullinnerpath]) 351 } 352 353 fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath 354 if out[fulldeepestpath] != "And this same flower that smiles to-day" { 355 t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) 356 } 357 358 fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease 359 if out[fullcheckrelease] != "Tomorrow will be dyin" { 360 t.Errorf("Unexpected release: %q", out[fullcheckrelease]) 361 } 362 } 363 364 func TestRenderBuiltinValues(t *testing.T) { 365 inner := &chart.Chart{ 366 Metadata: &chart.Metadata{Name: "Latium"}, 367 Templates: []*chart.Template{ 368 {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 369 {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, 370 }, 371 Values: &chart.Config{Raw: ``}, 372 Dependencies: []*chart.Chart{}, 373 Files: []*any.Any{ 374 {TypeUrl: "author", Value: []byte("Virgil")}, 375 {TypeUrl: "book/title.txt", Value: []byte("Aeneid")}, 376 }, 377 } 378 379 outer := &chart.Chart{ 380 Metadata: &chart.Metadata{Name: "Troy"}, 381 Templates: []*chart.Template{ 382 {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 383 }, 384 Values: &chart.Config{Raw: ``}, 385 Dependencies: []*chart.Chart{inner}, 386 } 387 388 inject := chartutil.Values{ 389 "Values": &chart.Config{Raw: ""}, 390 "Chart": outer.Metadata, 391 "Release": chartutil.Values{ 392 "Name": "Aeneid", 393 }, 394 } 395 396 t.Logf("Calculated values: %v", outer) 397 398 out, err := New().Render(outer, inject) 399 if err != nil { 400 t.Fatalf("failed to render templates: %s", err) 401 } 402 403 expects := map[string]string{ 404 "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", 405 "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid", 406 "Troy/charts/Latium/templates/From": "Virgil Aeneid", 407 } 408 for file, expect := range expects { 409 if out[file] != expect { 410 t.Errorf("Expected %q, got %q", expect, out[file]) 411 } 412 } 413 414 } 415 416 func TestAlterFuncMap(t *testing.T) { 417 c := &chart.Chart{ 418 Metadata: &chart.Metadata{Name: "conrad"}, 419 Templates: []*chart.Template{ 420 {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, 421 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, 422 }, 423 Values: &chart.Config{Raw: ``}, 424 Dependencies: []*chart.Chart{}, 425 } 426 427 // Check nested reference in include FuncMap 428 d := &chart.Chart{ 429 Metadata: &chart.Metadata{Name: "nested"}, 430 Templates: []*chart.Template{ 431 {Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)}, 432 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, 433 }, 434 } 435 436 v := chartutil.Values{ 437 "Values": &chart.Config{Raw: ""}, 438 "Chart": c.Metadata, 439 "Release": chartutil.Values{ 440 "Name": "Mistah Kurtz", 441 }, 442 } 443 444 out, err := New().Render(c, v) 445 if err != nil { 446 t.Fatal(err) 447 } 448 449 expect := " Mistah Kurtz - he dead." 450 if got := out["conrad/templates/quote"]; got != expect { 451 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 452 } 453 454 _, err = New().Render(d, v) 455 expectErrName := "nested/templates/quote" 456 if err == nil { 457 t.Errorf("Expected err of nested reference name: %v", expectErrName) 458 } 459 460 reqChart := &chart.Chart{ 461 Metadata: &chart.Metadata{Name: "conan"}, 462 Templates: []*chart.Template{ 463 {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, 464 {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, 465 }, 466 Values: &chart.Config{Raw: ``}, 467 Dependencies: []*chart.Chart{}, 468 } 469 470 reqValues := chartutil.Values{ 471 "Values": chartutil.Values{ 472 "who": "us", 473 "bases": 2, 474 }, 475 "Chart": reqChart.Metadata, 476 "Release": chartutil.Values{ 477 "Name": "That 90s meme", 478 }, 479 } 480 481 outReq, err := New().Render(reqChart, reqValues) 482 if err != nil { 483 t.Fatal(err) 484 } 485 expectStr := "All your base are belong to us" 486 if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr { 487 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq) 488 } 489 expectNum := "All 2 of them!" 490 if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum { 491 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq) 492 } 493 494 // test required without passing in needed values with lint mode on 495 // verifies lint replaces required with an empty string (should not fail) 496 lintValues := chartutil.Values{ 497 "Values": chartutil.Values{ 498 "who": "us", 499 }, 500 "Chart": reqChart.Metadata, 501 "Release": chartutil.Values{ 502 "Name": "That 90s meme", 503 }, 504 } 505 e := New() 506 e.LintMode = true 507 outReq, err = e.Render(reqChart, lintValues) 508 if err != nil { 509 t.Fatal(err) 510 } 511 expectStr = "All your base are belong to us" 512 if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr { 513 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq) 514 } 515 expectNum = "All of them!" 516 if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum { 517 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq) 518 } 519 520 tplChart := &chart.Chart{ 521 Metadata: &chart.Metadata{Name: "TplFunction"}, 522 Templates: []*chart.Template{ 523 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, 524 }, 525 Values: &chart.Config{Raw: ``}, 526 Dependencies: []*chart.Chart{}, 527 } 528 529 tplValues := chartutil.Values{ 530 "Values": chartutil.Values{ 531 "value": "myvalue", 532 }, 533 "Chart": tplChart.Metadata, 534 "Release": chartutil.Values{ 535 "Name": "TestRelease", 536 }, 537 } 538 539 outTpl, err := New().Render(tplChart, tplValues) 540 if err != nil { 541 t.Fatal(err) 542 } 543 544 expectTplStr := "Evaluate tpl Value: myvalue" 545 if gotStrTpl := outTpl["TplFunction/templates/base"]; gotStrTpl != expectTplStr { 546 t.Errorf("Expected %q, got %q (%v)", expectTplStr, gotStrTpl, outTpl) 547 } 548 549 tplChartWithFunction := &chart.Chart{ 550 Metadata: &chart.Metadata{Name: "TplFunction"}, 551 Templates: []*chart.Template{ 552 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, 553 }, 554 Values: &chart.Config{Raw: ``}, 555 Dependencies: []*chart.Chart{}, 556 } 557 558 tplValuesWithFunction := chartutil.Values{ 559 "Values": chartutil.Values{ 560 "value": "myvalue", 561 }, 562 "Chart": tplChartWithFunction.Metadata, 563 "Release": chartutil.Values{ 564 "Name": "TestRelease", 565 }, 566 } 567 568 outTplWithFunction, err := New().Render(tplChartWithFunction, tplValuesWithFunction) 569 if err != nil { 570 t.Fatal(err) 571 } 572 573 expectTplStrWithFunction := "Evaluate tpl Value: \"myvalue\"" 574 if gotStrTplWithFunction := outTplWithFunction["TplFunction/templates/base"]; gotStrTplWithFunction != expectTplStrWithFunction { 575 t.Errorf("Expected %q, got %q (%v)", expectTplStrWithFunction, gotStrTplWithFunction, outTplWithFunction) 576 } 577 578 tplChartWithInclude := &chart.Chart{ 579 Metadata: &chart.Metadata{Name: "TplFunction"}, 580 Templates: []*chart.Template{ 581 {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, 582 {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, 583 }, 584 Values: &chart.Config{Raw: ``}, 585 Dependencies: []*chart.Chart{}, 586 } 587 tplValueWithInclude := chartutil.Values{ 588 "Values": chartutil.Values{ 589 "value": "myvalue", 590 }, 591 "Chart": tplChartWithInclude.Metadata, 592 "Release": chartutil.Values{ 593 "Name": "TestRelease", 594 }, 595 } 596 597 outTplWithInclude, err := New().Render(tplChartWithInclude, tplValueWithInclude) 598 if err != nil { 599 t.Fatal(err) 600 } 601 602 expectedTplStrWithInclude := "\"TplFunction/templates/base\"" 603 if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude { 604 t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude) 605 } 606 607 } 608 609 func TestRenderRecursionLimit(t *testing.T) { 610 // endless recursion should produce an error 611 c := &chart.Chart{ 612 Metadata: &chart.Metadata{Name: "bad"}, 613 Templates: []*chart.Template{ 614 {Name: "templates/base", Data: []byte(`{{include "recursion" . }}`)}, 615 {Name: "templates/recursion", Data: []byte(`{{define "recursion"}}{{include "recursion" . }}{{end}}`)}, 616 }, 617 } 618 v := chartutil.Values{ 619 "Values": "", 620 "Chart": c.Metadata, 621 "Release": chartutil.Values{ 622 "Name": "TestRelease", 623 }, 624 } 625 expectErr := "rendering template has a nested reference name: recursion: unable to execute template" 626 627 e := New() 628 _, err := e.Render(c, v) 629 if err == nil || !strings.HasSuffix(err.Error(), expectErr) { 630 t.Errorf("Expected err with suffix: %s", expectErr) 631 } 632 633 // calling the same function many times is ok 634 times := 4000 635 phrase := "All work and no play makes Jack a dull boy" 636 printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}` 637 var repeatedIncl string 638 for i := 0; i < times; i++ { 639 repeatedIncl += `{{include "overlook" . }}` 640 } 641 642 d := &chart.Chart{ 643 Metadata: &chart.Metadata{Name: "overlook"}, 644 Templates: []*chart.Template{ 645 {Name: "templates/quote", Data: []byte(repeatedIncl)}, 646 {Name: "templates/_function", Data: []byte(printFunc)}, 647 }, 648 } 649 650 out, err := e.Render(d, v) 651 if err != nil { 652 t.Fatal(err) 653 } 654 655 var expect string 656 for i := 0; i < times; i++ { 657 expect += phrase + "\n" 658 } 659 if got := out["overlook/templates/quote"]; got != expect { 660 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 661 } 662 }