1
16
17 package engine
18
19 import (
20 "fmt"
21 "path"
22 "strings"
23 "sync"
24 "testing"
25 "text/template"
26
27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/client-go/dynamic"
31 "k8s.io/client-go/dynamic/fake"
32
33 "helm.sh/helm/v3/pkg/chart"
34 "helm.sh/helm/v3/pkg/chartutil"
35 )
36
37 func TestSortTemplates(t *testing.T) {
38 tpls := map[string]renderable{
39 "/mychart/templates/foo.tpl": {},
40 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {},
41 "/mychart/templates/bar.tpl": {},
42 "/mychart/templates/charts/foo/templates/bar.tpl": {},
43 "/mychart/templates/_foo.tpl": {},
44 "/mychart/templates/charts/foo/templates/foo.tpl": {},
45 "/mychart/templates/charts/bar/templates/foo.tpl": {},
46 }
47 got := sortTemplates(tpls)
48 if len(got) != len(tpls) {
49 t.Fatal("Sorted results are missing templates")
50 }
51
52 expect := []string{
53 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl",
54 "/mychart/templates/charts/foo/templates/foo.tpl",
55 "/mychart/templates/charts/foo/templates/bar.tpl",
56 "/mychart/templates/charts/bar/templates/foo.tpl",
57 "/mychart/templates/foo.tpl",
58 "/mychart/templates/bar.tpl",
59 "/mychart/templates/_foo.tpl",
60 }
61 for i, e := range expect {
62 if got[i] != e {
63 t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s",
64 strings.Join(expect, "\n"),
65 strings.Join(got, "\n"),
66 )
67 }
68 }
69 }
70
71 func TestFuncMap(t *testing.T) {
72 fns := funcMap()
73 forbidden := []string{"env", "expandenv"}
74 for _, f := range forbidden {
75 if _, ok := fns[f]; ok {
76 t.Errorf("Forbidden function %s exists in FuncMap.", f)
77 }
78 }
79
80
81 expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"}
82 for _, f := range expect {
83 if _, ok := fns[f]; !ok {
84 t.Errorf("Expected add-on function %q", f)
85 }
86 }
87 }
88
89 func TestRender(t *testing.T) {
90 c := &chart.Chart{
91 Metadata: &chart.Metadata{
92 Name: "moby",
93 Version: "1.2.3",
94 },
95 Templates: []*chart.File{
96 {Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")},
97 {Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")},
98 {Name: "templates/test3", Data: []byte("{{.noValue}}")},
99 {Name: "templates/test4", Data: []byte("{{toJson .Values}}")},
100 {Name: "templates/test5", Data: []byte("{{getHostByName \"helm.sh\"}}")},
101 },
102 Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"},
103 }
104
105 vals := map[string]interface{}{
106 "Values": map[string]interface{}{
107 "outer": "spouter",
108 "inner": "inn",
109 "global": map[string]interface{}{
110 "callme": "Ishmael",
111 },
112 },
113 }
114
115 v, err := chartutil.CoalesceValues(c, vals)
116 if err != nil {
117 t.Fatalf("Failed to coalesce values: %s", err)
118 }
119 out, err := Render(c, v)
120 if err != nil {
121 t.Errorf("Failed to render templates: %s", err)
122 }
123
124 expect := map[string]string{
125 "moby/templates/test1": "Spouter Inn",
126 "moby/templates/test2": "ishmael",
127 "moby/templates/test3": "",
128 "moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`,
129 "moby/templates/test5": "",
130 }
131
132 for name, data := range expect {
133 if out[name] != data {
134 t.Errorf("Expected %q, got %q", data, out[name])
135 }
136 }
137 }
138
139 func TestRenderRefsOrdering(t *testing.T) {
140 parentChart := &chart.Chart{
141 Metadata: &chart.Metadata{
142 Name: "parent",
143 Version: "1.2.3",
144 },
145 Templates: []*chart.File{
146 {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)},
147 {Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)},
148 },
149 }
150 childChart := &chart.Chart{
151 Metadata: &chart.Metadata{
152 Name: "child",
153 Version: "1.2.3",
154 },
155 Templates: []*chart.File{
156 {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)},
157 },
158 }
159 parentChart.AddDependency(childChart)
160
161 expect := map[string]string{
162 "parent/templates/test.yaml": "parent value",
163 }
164
165 for i := 0; i < 100; i++ {
166 out, err := Render(parentChart, chartutil.Values{})
167 if err != nil {
168 t.Fatalf("Failed to render templates: %s", err)
169 }
170
171 for name, data := range expect {
172 if out[name] != data {
173 t.Fatalf("Expected %q, got %q (iteration %d)", data, out[name], i+1)
174 }
175 }
176 }
177 }
178
179 func TestRenderInternals(t *testing.T) {
180
181
182 vals := chartutil.Values{"Name": "one", "Value": "two"}
183 tpls := map[string]renderable{
184 "one": {tpl: `Hello {{title .Name}}`, vals: vals},
185 "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals},
186
187
188 "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals},
189 }
190
191 out, err := new(Engine).render(tpls)
192 if err != nil {
193 t.Fatalf("Failed template rendering: %s", err)
194 }
195
196 if len(out) != 3 {
197 t.Fatalf("Expected 3 templates, got %d", len(out))
198 }
199
200 if out["one"] != "Hello One" {
201 t.Errorf("Expected 'Hello One', got %q", out["one"])
202 }
203
204 if out["two"] != "Goodbye TWO" {
205 t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"])
206 }
207
208 if out["three"] != "Goodbye THREE" {
209 t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"])
210 }
211 }
212
213 func TestRenderWithDNS(t *testing.T) {
214 c := &chart.Chart{
215 Metadata: &chart.Metadata{
216 Name: "moby",
217 Version: "1.2.3",
218 },
219 Templates: []*chart.File{
220 {Name: "templates/test1", Data: []byte("{{getHostByName \"helm.sh\"}}")},
221 },
222 Values: map[string]interface{}{},
223 }
224
225 vals := map[string]interface{}{
226 "Values": map[string]interface{}{},
227 }
228
229 v, err := chartutil.CoalesceValues(c, vals)
230 if err != nil {
231 t.Fatalf("Failed to coalesce values: %s", err)
232 }
233
234 var e Engine
235 e.EnableDNS = true
236 out, err := e.Render(c, v)
237 if err != nil {
238 t.Errorf("Failed to render templates: %s", err)
239 }
240
241 for _, val := range c.Templates {
242 fp := path.Join("moby", val.Name)
243 if out[fp] == "" {
244 t.Errorf("Expected IP address, got %q", out[fp])
245 }
246 }
247 }
248
249 type kindProps struct {
250 shouldErr error
251 gvr schema.GroupVersionResource
252 namespaced bool
253 }
254
255 type testClientProvider struct {
256 t *testing.T
257 scheme map[string]kindProps
258 objects []runtime.Object
259 }
260
261 func (p *testClientProvider) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) {
262 props := p.scheme[path.Join(apiVersion, kind)]
263 if props.shouldErr != nil {
264 return nil, false, props.shouldErr
265 }
266 return fake.NewSimpleDynamicClient(runtime.NewScheme(), p.objects...).Resource(props.gvr), props.namespaced, nil
267 }
268
269 var _ ClientProvider = &testClientProvider{}
270
271
272 func makeUnstructured(apiVersion, kind, name, namespace string) *unstructured.Unstructured {
273 ret := &unstructured.Unstructured{Object: map[string]interface{}{
274 "apiVersion": apiVersion,
275 "kind": kind,
276 "metadata": map[string]interface{}{
277 "name": name,
278 },
279 }}
280 if namespace != "" {
281 ret.Object["metadata"].(map[string]interface{})["namespace"] = namespace
282 }
283 return ret
284 }
285
286 func TestRenderWithClientProvider(t *testing.T) {
287 provider := &testClientProvider{
288 t: t,
289 scheme: map[string]kindProps{
290 "v1/Namespace": {
291 gvr: schema.GroupVersionResource{
292 Version: "v1",
293 Resource: "namespaces",
294 },
295 },
296 "v1/Pod": {
297 gvr: schema.GroupVersionResource{
298 Version: "v1",
299 Resource: "pods",
300 },
301 namespaced: true,
302 },
303 },
304 objects: []runtime.Object{
305 makeUnstructured("v1", "Namespace", "default", ""),
306 makeUnstructured("v1", "Pod", "pod1", "default"),
307 makeUnstructured("v1", "Pod", "pod2", "ns1"),
308 makeUnstructured("v1", "Pod", "pod3", "ns1"),
309 },
310 }
311
312 type testCase struct {
313 template string
314 output string
315 }
316 cases := map[string]testCase{
317 "ns-single": {
318 template: `{{ (lookup "v1" "Namespace" "" "default").metadata.name }}`,
319 output: "default",
320 },
321 "ns-list": {
322 template: `{{ (lookup "v1" "Namespace" "" "").items | len }}`,
323 output: "1",
324 },
325 "ns-missing": {
326 template: `{{ (lookup "v1" "Namespace" "" "absent") }}`,
327 output: "map[]",
328 },
329 "pod-single": {
330 template: `{{ (lookup "v1" "Pod" "default" "pod1").metadata.name }}`,
331 output: "pod1",
332 },
333 "pod-list": {
334 template: `{{ (lookup "v1" "Pod" "ns1" "").items | len }}`,
335 output: "2",
336 },
337 "pod-all": {
338 template: `{{ (lookup "v1" "Pod" "" "").items | len }}`,
339 output: "3",
340 },
341 "pod-missing": {
342 template: `{{ (lookup "v1" "Pod" "" "ns2") }}`,
343 output: "map[]",
344 },
345 }
346
347 c := &chart.Chart{
348 Metadata: &chart.Metadata{
349 Name: "moby",
350 Version: "1.2.3",
351 },
352 Values: map[string]interface{}{},
353 }
354
355 for name, exp := range cases {
356 c.Templates = append(c.Templates, &chart.File{
357 Name: path.Join("templates", name),
358 Data: []byte(exp.template),
359 })
360 }
361
362 vals := map[string]interface{}{
363 "Values": map[string]interface{}{},
364 }
365
366 v, err := chartutil.CoalesceValues(c, vals)
367 if err != nil {
368 t.Fatalf("Failed to coalesce values: %s", err)
369 }
370
371 out, err := RenderWithClientProvider(c, v, provider)
372 if err != nil {
373 t.Errorf("Failed to render templates: %s", err)
374 }
375
376 for name, want := range cases {
377 t.Run(name, func(t *testing.T) {
378 key := path.Join("moby/templates", name)
379 if out[key] != want.output {
380 t.Errorf("Expected %q, got %q", want, out[key])
381 }
382 })
383 }
384 }
385
386 func TestRenderWithClientProvider_error(t *testing.T) {
387 c := &chart.Chart{
388 Metadata: &chart.Metadata{
389 Name: "moby",
390 Version: "1.2.3",
391 },
392 Templates: []*chart.File{
393 {Name: "templates/error", Data: []byte(`{{ lookup "v1" "Error" "" "" }}`)},
394 },
395 Values: map[string]interface{}{},
396 }
397
398 vals := map[string]interface{}{
399 "Values": map[string]interface{}{},
400 }
401
402 v, err := chartutil.CoalesceValues(c, vals)
403 if err != nil {
404 t.Fatalf("Failed to coalesce values: %s", err)
405 }
406
407 provider := &testClientProvider{
408 t: t,
409 scheme: map[string]kindProps{
410 "v1/Error": {
411 shouldErr: fmt.Errorf("kaboom"),
412 },
413 },
414 }
415 _, err = RenderWithClientProvider(c, v, provider)
416 if err == nil || !strings.Contains(err.Error(), "kaboom") {
417 t.Errorf("Expected error from client provider when rendering, got %q", err)
418 }
419 }
420
421 func TestParallelRenderInternals(t *testing.T) {
422
423 e := new(Engine)
424 var wg sync.WaitGroup
425 for i := 0; i < 20; i++ {
426 wg.Add(1)
427 go func(i int) {
428 tt := fmt.Sprintf("expect-%d", i)
429 tpls := map[string]renderable{
430 "t": {
431 tpl: `{{.val}}`,
432 vals: map[string]interface{}{"val": tt},
433 },
434 }
435 out, err := e.render(tpls)
436 if err != nil {
437 t.Errorf("Failed to render %s: %s", tt, err)
438 }
439 if out["t"] != tt {
440 t.Errorf("Expected %q, got %q", tt, out["t"])
441 }
442 wg.Done()
443 }(i)
444 }
445 wg.Wait()
446 }
447
448 func TestParseErrors(t *testing.T) {
449 vals := chartutil.Values{"Values": map[string]interface{}{}}
450
451 tplsUndefinedFunction := map[string]renderable{
452 "undefined_function": {tpl: `{{foo}}`, vals: vals},
453 }
454 _, err := new(Engine).render(tplsUndefinedFunction)
455 if err == nil {
456 t.Fatalf("Expected failures while rendering: %s", err)
457 }
458 expected := `parse error at (undefined_function:1): function "foo" not defined`
459 if err.Error() != expected {
460 t.Errorf("Expected '%s', got %q", expected, err.Error())
461 }
462 }
463
464 func TestExecErrors(t *testing.T) {
465 vals := chartutil.Values{"Values": map[string]interface{}{}}
466 cases := []struct {
467 name string
468 tpls map[string]renderable
469 expected string
470 }{
471 {
472 name: "MissingRequired",
473 tpls: map[string]renderable{
474 "missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals},
475 },
476 expected: `execution error at (missing_required:1:2): foo is required`,
477 },
478 {
479 name: "MissingRequiredWithColons",
480 tpls: map[string]renderable{
481 "missing_required_with_colons": {tpl: `{{required ":this: message: has many: colons:" .Values.foo}}`, vals: vals},
482 },
483 expected: `execution error at (missing_required_with_colons:1:2): :this: message: has many: colons:`,
484 },
485 {
486 name: "Issue6044",
487 tpls: map[string]renderable{
488 "issue6044": {
489 vals: vals,
490 tpl: `{{ $someEmptyValue := "" }}
491 {{ $myvar := "abc" }}
492 {{- required (printf "%s: something is missing" $myvar) $someEmptyValue | repeat 0 }}`,
493 },
494 },
495 expected: `execution error at (issue6044:3:4): abc: something is missing`,
496 },
497 {
498 name: "MissingRequiredWithNewlines",
499 tpls: map[string]renderable{
500 "issue9981": {tpl: `{{required "foo is required\nmore info after the break" .Values.foo}}`, vals: vals},
501 },
502 expected: `execution error at (issue9981:1:2): foo is required
503 more info after the break`,
504 },
505 {
506 name: "FailWithNewlines",
507 tpls: map[string]renderable{
508 "issue9981": {tpl: `{{fail "something is wrong\nlinebreak"}}`, vals: vals},
509 },
510 expected: `execution error at (issue9981:1:2): something is wrong
511 linebreak`,
512 },
513 }
514
515 for _, tt := range cases {
516 t.Run(tt.name, func(t *testing.T) {
517 _, err := new(Engine).render(tt.tpls)
518 if err == nil {
519 t.Fatalf("Expected failures while rendering: %s", err)
520 }
521 if err.Error() != tt.expected {
522 t.Errorf("Expected %q, got %q", tt.expected, err.Error())
523 }
524 })
525 }
526 }
527
528 func TestFailErrors(t *testing.T) {
529 vals := chartutil.Values{"Values": map[string]interface{}{}}
530
531 failtpl := `All your base are belong to us{{ fail "This is an error" }}`
532 tplsFailed := map[string]renderable{
533 "failtpl": {tpl: failtpl, vals: vals},
534 }
535 _, err := new(Engine).render(tplsFailed)
536 if err == nil {
537 t.Fatalf("Expected failures while rendering: %s", err)
538 }
539 expected := `execution error at (failtpl:1:33): This is an error`
540 if err.Error() != expected {
541 t.Errorf("Expected '%s', got %q", expected, err.Error())
542 }
543
544 var e Engine
545 e.LintMode = true
546 out, err := e.render(tplsFailed)
547 if err != nil {
548 t.Fatal(err)
549 }
550
551 expectStr := "All your base are belong to us"
552 if gotStr := out["failtpl"]; gotStr != expectStr {
553 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
554 }
555 }
556
557 func TestAllTemplates(t *testing.T) {
558 ch1 := &chart.Chart{
559 Metadata: &chart.Metadata{Name: "ch1"},
560 Templates: []*chart.File{
561 {Name: "templates/foo", Data: []byte("foo")},
562 {Name: "templates/bar", Data: []byte("bar")},
563 },
564 }
565 dep1 := &chart.Chart{
566 Metadata: &chart.Metadata{Name: "laboratory mice"},
567 Templates: []*chart.File{
568 {Name: "templates/pinky", Data: []byte("pinky")},
569 {Name: "templates/brain", Data: []byte("brain")},
570 },
571 }
572 ch1.AddDependency(dep1)
573
574 dep2 := &chart.Chart{
575 Metadata: &chart.Metadata{Name: "same thing we do every night"},
576 Templates: []*chart.File{
577 {Name: "templates/innermost", Data: []byte("innermost")},
578 },
579 }
580 dep1.AddDependency(dep2)
581
582 tpls := allTemplates(ch1, chartutil.Values{})
583 if len(tpls) != 5 {
584 t.Errorf("Expected 5 charts, got %d", len(tpls))
585 }
586 }
587
588 func TestChartValuesContainsIsRoot(t *testing.T) {
589 ch1 := &chart.Chart{
590 Metadata: &chart.Metadata{Name: "parent"},
591 Templates: []*chart.File{
592 {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")},
593 },
594 }
595 dep1 := &chart.Chart{
596 Metadata: &chart.Metadata{Name: "child"},
597 Templates: []*chart.File{
598 {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")},
599 },
600 }
601 ch1.AddDependency(dep1)
602
603 out, err := Render(ch1, chartutil.Values{})
604 if err != nil {
605 t.Fatalf("failed to render templates: %s", err)
606 }
607 expects := map[string]string{
608 "parent/charts/child/templates/isroot": "false",
609 "parent/templates/isroot": "true",
610 }
611 for file, expect := range expects {
612 if out[file] != expect {
613 t.Errorf("Expected %q, got %q", expect, out[file])
614 }
615 }
616 }
617
618 func TestRenderDependency(t *testing.T) {
619 deptpl := `{{define "myblock"}}World{{end}}`
620 toptpl := `Hello {{template "myblock"}}`
621 ch := &chart.Chart{
622 Metadata: &chart.Metadata{Name: "outerchart"},
623 Templates: []*chart.File{
624 {Name: "templates/outer", Data: []byte(toptpl)},
625 },
626 }
627 ch.AddDependency(&chart.Chart{
628 Metadata: &chart.Metadata{Name: "innerchart"},
629 Templates: []*chart.File{
630 {Name: "templates/inner", Data: []byte(deptpl)},
631 },
632 })
633
634 out, err := Render(ch, map[string]interface{}{})
635 if err != nil {
636 t.Fatalf("failed to render chart: %s", err)
637 }
638
639 if len(out) != 2 {
640 t.Errorf("Expected 2, got %d", len(out))
641 }
642
643 expect := "Hello World"
644 if out["outerchart/templates/outer"] != expect {
645 t.Errorf("Expected %q, got %q", expect, out["outer"])
646 }
647
648 }
649
650 func TestRenderNestedValues(t *testing.T) {
651 innerpath := "templates/inner.tpl"
652 outerpath := "templates/outer.tpl"
653
654 deepestpath := "templates/inner.tpl"
655 checkrelease := "templates/release.tpl"
656
657 subchartspath := "templates/subcharts.tpl"
658
659 deepest := &chart.Chart{
660 Metadata: &chart.Metadata{Name: "deepest"},
661 Templates: []*chart.File{
662 {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)},
663 {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)},
664 },
665 Values: map[string]interface{}{"what": "milkshake", "where": "here"},
666 }
667
668 inner := &chart.Chart{
669 Metadata: &chart.Metadata{Name: "herrick"},
670 Templates: []*chart.File{
671 {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)},
672 },
673 Values: map[string]interface{}{"who": "Robert", "what": "glasses"},
674 }
675 inner.AddDependency(deepest)
676
677 outer := &chart.Chart{
678 Metadata: &chart.Metadata{Name: "top"},
679 Templates: []*chart.File{
680 {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)},
681 {Name: subchartspath, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)},
682 },
683 Values: map[string]interface{}{
684 "what": "stinkweed",
685 "who": "me",
686 "herrick": map[string]interface{}{
687 "who": "time",
688 "what": "Sun",
689 },
690 },
691 }
692 outer.AddDependency(inner)
693
694 injValues := map[string]interface{}{
695 "what": "rosebuds",
696 "herrick": map[string]interface{}{
697 "deepest": map[string]interface{}{
698 "what": "flower",
699 "where": "Heaven",
700 },
701 },
702 "global": map[string]interface{}{
703 "when": "to-day",
704 },
705 }
706
707 tmp, err := chartutil.CoalesceValues(outer, injValues)
708 if err != nil {
709 t.Fatalf("Failed to coalesce values: %s", err)
710 }
711
712 inject := chartutil.Values{
713 "Values": tmp,
714 "Chart": outer.Metadata,
715 "Release": chartutil.Values{
716 "Name": "dyin",
717 },
718 }
719
720 t.Logf("Calculated values: %v", inject)
721
722 out, err := Render(outer, inject)
723 if err != nil {
724 t.Fatalf("failed to render templates: %s", err)
725 }
726
727 fullouterpath := "top/" + outerpath
728 if out[fullouterpath] != "Gather ye rosebuds while ye may" {
729 t.Errorf("Unexpected outer: %q", out[fullouterpath])
730 }
731
732 fullinnerpath := "top/charts/herrick/" + innerpath
733 if out[fullinnerpath] != "Old time is still a-flyin'" {
734 t.Errorf("Unexpected inner: %q", out[fullinnerpath])
735 }
736
737 fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath
738 if out[fulldeepestpath] != "And this same flower that smiles to-day" {
739 t.Errorf("Unexpected deepest: %q", out[fulldeepestpath])
740 }
741
742 fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease
743 if out[fullcheckrelease] != "Tomorrow will be dyin" {
744 t.Errorf("Unexpected release: %q", out[fullcheckrelease])
745 }
746
747 fullchecksubcharts := "top/" + subchartspath
748 if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" {
749 t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts])
750 }
751 }
752
753 func TestRenderBuiltinValues(t *testing.T) {
754 inner := &chart.Chart{
755 Metadata: &chart.Metadata{Name: "Latium"},
756 Templates: []*chart.File{
757 {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
758 {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)},
759 },
760 Files: []*chart.File{
761 {Name: "author", Data: []byte("Virgil")},
762 {Name: "book/title.txt", Data: []byte("Aeneid")},
763 },
764 }
765
766 outer := &chart.Chart{
767 Metadata: &chart.Metadata{Name: "Troy"},
768 Templates: []*chart.File{
769 {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
770 {Name: "templates/Amata", Data: []byte(`{{.Subcharts.Latium.Chart.Name}} {{.Subcharts.Latium.Files.author | printf "%s"}}`)},
771 },
772 }
773 outer.AddDependency(inner)
774
775 inject := chartutil.Values{
776 "Values": "",
777 "Chart": outer.Metadata,
778 "Release": chartutil.Values{
779 "Name": "Aeneid",
780 },
781 }
782
783 t.Logf("Calculated values: %v", outer)
784
785 out, err := Render(outer, inject)
786 if err != nil {
787 t.Fatalf("failed to render templates: %s", err)
788 }
789
790 expects := map[string]string{
791 "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid",
792 "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid",
793 "Troy/templates/Amata": "Latium Virgil",
794 "Troy/charts/Latium/templates/From": "Virgil Aeneid",
795 }
796 for file, expect := range expects {
797 if out[file] != expect {
798 t.Errorf("Expected %q, got %q", expect, out[file])
799 }
800 }
801
802 }
803
804 func TestAlterFuncMap_include(t *testing.T) {
805 c := &chart.Chart{
806 Metadata: &chart.Metadata{Name: "conrad"},
807 Templates: []*chart.File{
808 {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)},
809 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
810 },
811 }
812
813
814 d := &chart.Chart{
815 Metadata: &chart.Metadata{Name: "nested"},
816 Templates: []*chart.File{
817 {Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)},
818 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
819 },
820 }
821
822 v := chartutil.Values{
823 "Values": "",
824 "Chart": c.Metadata,
825 "Release": chartutil.Values{
826 "Name": "Mistah Kurtz",
827 },
828 }
829
830 out, err := Render(c, v)
831 if err != nil {
832 t.Fatal(err)
833 }
834
835 expect := " Mistah Kurtz - he dead."
836 if got := out["conrad/templates/quote"]; got != expect {
837 t.Errorf("Expected %q, got %q (%v)", expect, got, out)
838 }
839
840 _, err = Render(d, v)
841 expectErrName := "nested/templates/quote"
842 if err == nil {
843 t.Errorf("Expected err of nested reference name: %v", expectErrName)
844 }
845 }
846
847 func TestAlterFuncMap_require(t *testing.T) {
848 c := &chart.Chart{
849 Metadata: &chart.Metadata{Name: "conan"},
850 Templates: []*chart.File{
851 {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)},
852 {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)},
853 },
854 }
855
856 v := chartutil.Values{
857 "Values": chartutil.Values{
858 "who": "us",
859 "bases": 2,
860 },
861 "Chart": c.Metadata,
862 "Release": chartutil.Values{
863 "Name": "That 90s meme",
864 },
865 }
866
867 out, err := Render(c, v)
868 if err != nil {
869 t.Fatal(err)
870 }
871
872 expectStr := "All your base are belong to us"
873 if gotStr := out["conan/templates/quote"]; gotStr != expectStr {
874 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
875 }
876 expectNum := "All 2 of them!"
877 if gotNum := out["conan/templates/bases"]; gotNum != expectNum {
878 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out)
879 }
880
881
882
883 lintValues := chartutil.Values{
884 "Values": chartutil.Values{
885 "who": "us",
886 },
887 "Chart": c.Metadata,
888 "Release": chartutil.Values{
889 "Name": "That 90s meme",
890 },
891 }
892 var e Engine
893 e.LintMode = true
894 out, err = e.Render(c, lintValues)
895 if err != nil {
896 t.Fatal(err)
897 }
898
899 expectStr = "All your base are belong to us"
900 if gotStr := out["conan/templates/quote"]; gotStr != expectStr {
901 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
902 }
903 expectNum = "All of them!"
904 if gotNum := out["conan/templates/bases"]; gotNum != expectNum {
905 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out)
906 }
907 }
908
909 func TestAlterFuncMap_tpl(t *testing.T) {
910 c := &chart.Chart{
911 Metadata: &chart.Metadata{Name: "TplFunction"},
912 Templates: []*chart.File{
913 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)},
914 },
915 }
916
917 v := chartutil.Values{
918 "Values": chartutil.Values{
919 "value": "myvalue",
920 },
921 "Chart": c.Metadata,
922 "Release": chartutil.Values{
923 "Name": "TestRelease",
924 },
925 }
926
927 out, err := Render(c, v)
928 if err != nil {
929 t.Fatal(err)
930 }
931
932 expect := "Evaluate tpl Value: myvalue"
933 if got := out["TplFunction/templates/base"]; got != expect {
934 t.Errorf("Expected %q, got %q (%v)", expect, got, out)
935 }
936 }
937
938 func TestAlterFuncMap_tplfunc(t *testing.T) {
939 c := &chart.Chart{
940 Metadata: &chart.Metadata{Name: "TplFunction"},
941 Templates: []*chart.File{
942 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)},
943 },
944 }
945
946 v := chartutil.Values{
947 "Values": chartutil.Values{
948 "value": "myvalue",
949 },
950 "Chart": c.Metadata,
951 "Release": chartutil.Values{
952 "Name": "TestRelease",
953 },
954 }
955
956 out, err := Render(c, v)
957 if err != nil {
958 t.Fatal(err)
959 }
960
961 expect := "Evaluate tpl Value: \"myvalue\""
962 if got := out["TplFunction/templates/base"]; got != expect {
963 t.Errorf("Expected %q, got %q (%v)", expect, got, out)
964 }
965 }
966
967 func TestAlterFuncMap_tplinclude(t *testing.T) {
968 c := &chart.Chart{
969 Metadata: &chart.Metadata{Name: "TplFunction"},
970 Templates: []*chart.File{
971 {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)},
972 {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)},
973 },
974 }
975 v := chartutil.Values{
976 "Values": chartutil.Values{
977 "value": "myvalue",
978 },
979 "Chart": c.Metadata,
980 "Release": chartutil.Values{
981 "Name": "TestRelease",
982 },
983 }
984
985 out, err := Render(c, v)
986 if err != nil {
987 t.Fatal(err)
988 }
989
990 expect := "\"TplFunction/templates/base\""
991 if got := out["TplFunction/templates/base"]; got != expect {
992 t.Errorf("Expected %q, got %q (%v)", expect, got, out)
993 }
994
995 }
996
997 func TestRenderRecursionLimit(t *testing.T) {
998
999 c := &chart.Chart{
1000 Metadata: &chart.Metadata{Name: "bad"},
1001 Templates: []*chart.File{
1002 {Name: "templates/base", Data: []byte(`{{include "recursion" . }}`)},
1003 {Name: "templates/recursion", Data: []byte(`{{define "recursion"}}{{include "recursion" . }}{{end}}`)},
1004 },
1005 }
1006 v := chartutil.Values{
1007 "Values": "",
1008 "Chart": c.Metadata,
1009 "Release": chartutil.Values{
1010 "Name": "TestRelease",
1011 },
1012 }
1013 expectErr := "rendering template has a nested reference name: recursion: unable to execute template"
1014
1015 _, err := Render(c, v)
1016 if err == nil || !strings.HasSuffix(err.Error(), expectErr) {
1017 t.Errorf("Expected err with suffix: %s", expectErr)
1018 }
1019
1020
1021 times := 4000
1022 phrase := "All work and no play makes Jack a dull boy"
1023 printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}`
1024 var repeatedIncl string
1025 for i := 0; i < times; i++ {
1026 repeatedIncl += `{{include "overlook" . }}`
1027 }
1028
1029 d := &chart.Chart{
1030 Metadata: &chart.Metadata{Name: "overlook"},
1031 Templates: []*chart.File{
1032 {Name: "templates/quote", Data: []byte(repeatedIncl)},
1033 {Name: "templates/_function", Data: []byte(printFunc)},
1034 },
1035 }
1036
1037 out, err := Render(d, v)
1038 if err != nil {
1039 t.Fatal(err)
1040 }
1041
1042 var expect string
1043 for i := 0; i < times; i++ {
1044 expect += phrase + "\n"
1045 }
1046 if got := out["overlook/templates/quote"]; got != expect {
1047 t.Errorf("Expected %q, got %q (%v)", expect, got, out)
1048 }
1049
1050 }
1051
1052 func TestRenderLoadTemplateForTplFromFile(t *testing.T) {
1053 c := &chart.Chart{
1054 Metadata: &chart.Metadata{Name: "TplLoadFromFile"},
1055 Templates: []*chart.File{
1056 {Name: "templates/base", Data: []byte(`{{ tpl (.Files.Get .Values.filename) . }}`)},
1057 {Name: "templates/_function", Data: []byte(`{{define "test-function"}}test-function{{end}}`)},
1058 },
1059 Files: []*chart.File{
1060 {Name: "test", Data: []byte(`{{ tpl (.Files.Get .Values.filename2) .}}`)},
1061 {Name: "test2", Data: []byte(`{{include "test-function" .}}{{define "nested-define"}}nested-define-content{{end}} {{include "nested-define" .}}`)},
1062 },
1063 }
1064
1065 v := chartutil.Values{
1066 "Values": chartutil.Values{
1067 "filename": "test",
1068 "filename2": "test2",
1069 },
1070 "Chart": c.Metadata,
1071 "Release": chartutil.Values{
1072 "Name": "TestRelease",
1073 },
1074 }
1075
1076 out, err := Render(c, v)
1077 if err != nil {
1078 t.Fatal(err)
1079 }
1080
1081 expect := "test-function nested-define-content"
1082 if got := out["TplLoadFromFile/templates/base"]; got != expect {
1083 t.Fatalf("Expected %q, got %q", expect, got)
1084 }
1085 }
1086
1087 func TestRenderTplEmpty(t *testing.T) {
1088 c := &chart.Chart{
1089 Metadata: &chart.Metadata{Name: "TplEmpty"},
1090 Templates: []*chart.File{
1091 {Name: "templates/empty-string", Data: []byte(`{{tpl "" .}}`)},
1092 {Name: "templates/empty-action", Data: []byte(`{{tpl "{{ \"\"}}" .}}`)},
1093 {Name: "templates/only-defines", Data: []byte(`{{tpl "{{define \"not-invoked\"}}not-rendered{{end}}" .}}`)},
1094 },
1095 }
1096 v := chartutil.Values{
1097 "Chart": c.Metadata,
1098 "Release": chartutil.Values{
1099 "Name": "TestRelease",
1100 },
1101 }
1102
1103 out, err := Render(c, v)
1104 if err != nil {
1105 t.Fatal(err)
1106 }
1107
1108 expects := map[string]string{
1109 "TplEmpty/templates/empty-string": "",
1110 "TplEmpty/templates/empty-action": "",
1111 "TplEmpty/templates/only-defines": "",
1112 }
1113 for file, expect := range expects {
1114 if out[file] != expect {
1115 t.Errorf("Expected %q, got %q", expect, out[file])
1116 }
1117 }
1118 }
1119
1120 func TestRenderTplTemplateNames(t *testing.T) {
1121
1122 c := &chart.Chart{
1123 Metadata: &chart.Metadata{Name: "TplTemplateNames"},
1124 Templates: []*chart.File{
1125 {Name: "templates/default-basepath", Data: []byte(`{{tpl "{{ .Template.BasePath }}" .}}`)},
1126 {Name: "templates/default-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .}}`)},
1127 {Name: "templates/modified-basepath", Data: []byte(`{{tpl "{{ .Template.BasePath }}" .Values.dot}}`)},
1128 {Name: "templates/modified-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .Values.dot}}`)},
1129 {Name: "templates/modified-field", Data: []byte(`{{tpl "{{ .Template.Field }}" .Values.dot}}`)},
1130 },
1131 }
1132 v := chartutil.Values{
1133 "Values": chartutil.Values{
1134 "dot": chartutil.Values{
1135 "Template": chartutil.Values{
1136 "BasePath": "path/to/template",
1137 "Name": "name-of-template",
1138 "Field": "extra-field",
1139 },
1140 },
1141 },
1142 "Chart": c.Metadata,
1143 "Release": chartutil.Values{
1144 "Name": "TestRelease",
1145 },
1146 }
1147
1148 out, err := Render(c, v)
1149 if err != nil {
1150 t.Fatal(err)
1151 }
1152
1153 expects := map[string]string{
1154 "TplTemplateNames/templates/default-basepath": "TplTemplateNames/templates",
1155 "TplTemplateNames/templates/default-name": "TplTemplateNames/templates/default-name",
1156 "TplTemplateNames/templates/modified-basepath": "path/to/template",
1157 "TplTemplateNames/templates/modified-name": "name-of-template",
1158 "TplTemplateNames/templates/modified-field": "extra-field",
1159 }
1160 for file, expect := range expects {
1161 if out[file] != expect {
1162 t.Errorf("Expected %q, got %q", expect, out[file])
1163 }
1164 }
1165 }
1166
1167 func TestRenderTplRedefines(t *testing.T) {
1168
1169 c := &chart.Chart{
1170 Metadata: &chart.Metadata{Name: "TplRedefines"},
1171 Templates: []*chart.File{
1172 {Name: "templates/_partials", Data: []byte(`{{define "partial"}}original-in-partial{{end}}`)},
1173 {Name: "templates/partial", Data: []byte(
1174 `before: {{include "partial" .}}\n{{tpl .Values.partialText .}}\nafter: {{include "partial" .}}`,
1175 )},
1176 {Name: "templates/manifest", Data: []byte(
1177 `{{define "manifest"}}original-in-manifest{{end}}` +
1178 `before: {{include "manifest" .}}\n{{tpl .Values.manifestText .}}\nafter: {{include "manifest" .}}`,
1179 )},
1180 {Name: "templates/manifest-only", Data: []byte(
1181 `{{define "manifest-only"}}only-in-manifest{{end}}` +
1182 `before: {{include "manifest-only" .}}\n{{tpl .Values.manifestOnlyText .}}\nafter: {{include "manifest-only" .}}`,
1183 )},
1184 {Name: "templates/nested", Data: []byte(
1185 `{{define "nested"}}original-in-manifest{{end}}` +
1186 `{{define "nested-outer"}}original-outer-in-manifest{{end}}` +
1187 `before: {{include "nested" .}} {{include "nested-outer" .}}\n` +
1188 `{{tpl .Values.nestedText .}}\n` +
1189 `after: {{include "nested" .}} {{include "nested-outer" .}}`,
1190 )},
1191 },
1192 }
1193 v := chartutil.Values{
1194 "Values": chartutil.Values{
1195 "partialText": `{{define "partial"}}redefined-in-tpl{{end}}tpl: {{include "partial" .}}`,
1196 "manifestText": `{{define "manifest"}}redefined-in-tpl{{end}}tpl: {{include "manifest" .}}`,
1197 "manifestOnlyText": `tpl: {{include "manifest-only" .}}`,
1198 "nestedText": `{{define "nested"}}redefined-in-tpl{{end}}` +
1199 `{{define "nested-outer"}}redefined-outer-in-tpl{{end}}` +
1200 `before-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}\n` +
1201 `{{tpl .Values.innerText .}}\n` +
1202 `after-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`,
1203 "innerText": `{{define "nested"}}redefined-in-inner-tpl{{end}}inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`,
1204 },
1205 "Chart": c.Metadata,
1206 "Release": chartutil.Values{
1207 "Name": "TestRelease",
1208 },
1209 }
1210
1211 out, err := Render(c, v)
1212 if err != nil {
1213 t.Fatal(err)
1214 }
1215
1216 expects := map[string]string{
1217 "TplRedefines/templates/partial": `before: original-in-partial\ntpl: redefined-in-tpl\nafter: original-in-partial`,
1218 "TplRedefines/templates/manifest": `before: original-in-manifest\ntpl: redefined-in-tpl\nafter: original-in-manifest`,
1219 "TplRedefines/templates/manifest-only": `before: only-in-manifest\ntpl: only-in-manifest\nafter: only-in-manifest`,
1220 "TplRedefines/templates/nested": `before: original-in-manifest original-outer-in-manifest\n` +
1221 `before-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` +
1222 `inner-tpl: redefined-in-inner-tpl redefined-outer-in-tpl\n` +
1223 `after-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` +
1224 `after: original-in-manifest original-outer-in-manifest`,
1225 }
1226 for file, expect := range expects {
1227 if out[file] != expect {
1228 t.Errorf("Expected %q, got %q", expect, out[file])
1229 }
1230 }
1231 }
1232
1233 func TestRenderTplMissingKey(t *testing.T) {
1234
1235 c := &chart.Chart{
1236 Metadata: &chart.Metadata{Name: "TplMissingKey"},
1237 Templates: []*chart.File{
1238 {Name: "templates/manifest", Data: []byte(
1239 `missingValue: {{tpl "{{.Values.noSuchKey}}" .}}`,
1240 )},
1241 },
1242 }
1243 v := chartutil.Values{
1244 "Values": chartutil.Values{},
1245 "Chart": c.Metadata,
1246 "Release": chartutil.Values{
1247 "Name": "TestRelease",
1248 },
1249 }
1250
1251 out, err := Render(c, v)
1252 if err != nil {
1253 t.Fatal(err)
1254 }
1255
1256 expects := map[string]string{
1257 "TplMissingKey/templates/manifest": `missingValue: `,
1258 }
1259 for file, expect := range expects {
1260 if out[file] != expect {
1261 t.Errorf("Expected %q, got %q", expect, out[file])
1262 }
1263 }
1264 }
1265
1266 func TestRenderTplMissingKeyString(t *testing.T) {
1267
1268 c := &chart.Chart{
1269 Metadata: &chart.Metadata{Name: "TplMissingKeyStrict"},
1270 Templates: []*chart.File{
1271 {Name: "templates/manifest", Data: []byte(
1272 `missingValue: {{tpl "{{.Values.noSuchKey}}" .}}`,
1273 )},
1274 },
1275 }
1276 v := chartutil.Values{
1277 "Values": chartutil.Values{},
1278 "Chart": c.Metadata,
1279 "Release": chartutil.Values{
1280 "Name": "TestRelease",
1281 },
1282 }
1283
1284 e := new(Engine)
1285 e.Strict = true
1286
1287 out, err := e.Render(c, v)
1288 if err == nil {
1289 t.Errorf("Expected error, got %v", out)
1290 return
1291 }
1292 switch err.(type) {
1293 case (template.ExecError):
1294 errTxt := fmt.Sprint(err)
1295 if !strings.Contains(errTxt, "noSuchKey") {
1296 t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt)
1297 }
1298 default:
1299
1300 t.Fatal(err)
1301 }
1302 }
1303
View as plain text