mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
592 lines
16 KiB
592 lines
16 KiB
/*
|
|
Copyright The Helm Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
|
|
"k8s.io/helm/pkg/chartutil"
|
|
"k8s.io/helm/pkg/proto/hapi/chart"
|
|
|
|
"github.com/golang/protobuf/ptypes/any"
|
|
)
|
|
|
|
func TestSortTemplates(t *testing.T) {
|
|
tpls := map[string]renderable{
|
|
"/mychart/templates/foo.tpl": {},
|
|
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {},
|
|
"/mychart/templates/bar.tpl": {},
|
|
"/mychart/templates/charts/foo/templates/bar.tpl": {},
|
|
"/mychart/templates/_foo.tpl": {},
|
|
"/mychart/templates/charts/foo/templates/foo.tpl": {},
|
|
"/mychart/templates/charts/bar/templates/foo.tpl": {},
|
|
}
|
|
got := sortTemplates(tpls)
|
|
if len(got) != len(tpls) {
|
|
t.Fatal("Sorted results are missing templates")
|
|
}
|
|
|
|
expect := []string{
|
|
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl",
|
|
"/mychart/templates/charts/foo/templates/foo.tpl",
|
|
"/mychart/templates/charts/foo/templates/bar.tpl",
|
|
"/mychart/templates/charts/bar/templates/foo.tpl",
|
|
"/mychart/templates/foo.tpl",
|
|
"/mychart/templates/bar.tpl",
|
|
"/mychart/templates/_foo.tpl",
|
|
}
|
|
for i, e := range expect {
|
|
if got[i] != e {
|
|
t.Errorf("expected %q, got %q at index %d\n\tExp: %#v\n\tGot: %#v", e, got[i], i, expect, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEngine(t *testing.T) {
|
|
e := New()
|
|
|
|
// Forbidden because they allow access to the host OS.
|
|
forbidden := []string{"env", "expandenv"}
|
|
for _, f := range forbidden {
|
|
if _, ok := e.FuncMap[f]; ok {
|
|
t.Errorf("Forbidden function %s exists in FuncMap.", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFuncMap(t *testing.T) {
|
|
fns := FuncMap()
|
|
forbidden := []string{"env", "expandenv"}
|
|
for _, f := range forbidden {
|
|
if _, ok := fns[f]; ok {
|
|
t.Errorf("Forbidden function %s exists in FuncMap.", f)
|
|
}
|
|
}
|
|
|
|
// Test for Engine-specific template functions.
|
|
expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"}
|
|
for _, f := range expect {
|
|
if _, ok := fns[f]; !ok {
|
|
t.Errorf("Expected add-on function %q", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRender(t *testing.T) {
|
|
c := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "moby",
|
|
Version: "1.2.3",
|
|
},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/test1", Data: []byte("{{.outer | title }} {{.inner | title}}")},
|
|
{Name: "templates/test2", Data: []byte("{{.global.callme | lower }}")},
|
|
{Name: "templates/test3", Data: []byte("{{.noValue}}")},
|
|
},
|
|
Values: &chart.Config{
|
|
Raw: "outer: DEFAULT\ninner: DEFAULT",
|
|
},
|
|
}
|
|
|
|
vals := &chart.Config{
|
|
Raw: `
|
|
outer: spouter
|
|
inner: inn
|
|
global:
|
|
callme: Ishmael
|
|
`}
|
|
|
|
e := New()
|
|
v, err := chartutil.CoalesceValues(c, vals)
|
|
if err != nil {
|
|
t.Fatalf("Failed to coalesce values: %s", err)
|
|
}
|
|
out, err := e.Render(c, v)
|
|
if err != nil {
|
|
t.Errorf("Failed to render templates: %s", err)
|
|
}
|
|
|
|
expect := "Spouter Inn"
|
|
if out["moby/templates/test1"] != expect {
|
|
t.Errorf("Expected %q, got %q", expect, out["test1"])
|
|
}
|
|
|
|
expect = "ishmael"
|
|
if out["moby/templates/test2"] != expect {
|
|
t.Errorf("Expected %q, got %q", expect, out["test2"])
|
|
}
|
|
expect = ""
|
|
if out["moby/templates/test3"] != expect {
|
|
t.Errorf("Expected %q, got %q", expect, out["test3"])
|
|
}
|
|
|
|
if _, err := e.Render(c, v); err != nil {
|
|
t.Errorf("Unexpected error: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestRenderInternals(t *testing.T) {
|
|
// Test the internals of the rendering tool.
|
|
e := New()
|
|
|
|
vals := chartutil.Values{"Name": "one", "Value": "two"}
|
|
tpls := map[string]renderable{
|
|
"one": {tpl: `Hello {{title .Name}}`, vals: vals},
|
|
"two": {tpl: `Goodbye {{upper .Value}}`, vals: vals},
|
|
// Test whether a template can reliably reference another template
|
|
// without regard for ordering.
|
|
"three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals},
|
|
}
|
|
|
|
out, err := e.render(tpls)
|
|
if err != nil {
|
|
t.Fatalf("Failed template rendering: %s", err)
|
|
}
|
|
|
|
if len(out) != 3 {
|
|
t.Fatalf("Expected 3 templates, got %d", len(out))
|
|
}
|
|
|
|
if out["one"] != "Hello One" {
|
|
t.Errorf("Expected 'Hello One', got %q", out["one"])
|
|
}
|
|
|
|
if out["two"] != "Goodbye TWO" {
|
|
t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"])
|
|
}
|
|
|
|
if out["three"] != "Goodbye THREE" {
|
|
t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"])
|
|
}
|
|
}
|
|
|
|
func TestParallelRenderInternals(t *testing.T) {
|
|
// Make sure that we can use one Engine to run parallel template renders.
|
|
e := New()
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
fname := "my/file/name"
|
|
tt := fmt.Sprintf("expect-%d", i)
|
|
v := chartutil.Values{"val": tt}
|
|
tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}}
|
|
out, err := e.render(tpls)
|
|
if err != nil {
|
|
t.Errorf("Failed to render %s: %s", tt, err)
|
|
}
|
|
if out[fname] != tt {
|
|
t.Errorf("Expected %q, got %q", tt, out[fname])
|
|
}
|
|
wg.Done()
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestAllTemplates(t *testing.T) {
|
|
ch1 := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "ch1"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/foo", Data: []byte("foo")},
|
|
{Name: "templates/bar", Data: []byte("bar")},
|
|
},
|
|
Dependencies: []*chart.Chart{
|
|
{
|
|
Metadata: &chart.Metadata{Name: "laboratory mice"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/pinky", Data: []byte("pinky")},
|
|
{Name: "templates/brain", Data: []byte("brain")},
|
|
},
|
|
Dependencies: []*chart.Chart{{
|
|
Metadata: &chart.Metadata{Name: "same thing we do every night"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/innermost", Data: []byte("innermost")},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var v chartutil.Values
|
|
tpls := allTemplates(ch1, v)
|
|
if len(tpls) != 5 {
|
|
t.Errorf("Expected 5 charts, got %d", len(tpls))
|
|
}
|
|
}
|
|
|
|
func TestRenderDependency(t *testing.T) {
|
|
e := New()
|
|
deptpl := `{{define "myblock"}}World{{end}}`
|
|
toptpl := `Hello {{template "myblock"}}`
|
|
ch := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "outerchart"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/outer", Data: []byte(toptpl)},
|
|
},
|
|
Dependencies: []*chart.Chart{
|
|
{
|
|
Metadata: &chart.Metadata{Name: "innerchart"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/inner", Data: []byte(deptpl)},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
out, err := e.Render(ch, map[string]interface{}{})
|
|
|
|
if err != nil {
|
|
t.Fatalf("failed to render chart: %s", err)
|
|
}
|
|
|
|
if len(out) != 2 {
|
|
t.Errorf("Expected 2, got %d", len(out))
|
|
}
|
|
|
|
expect := "Hello World"
|
|
if out["outerchart/templates/outer"] != expect {
|
|
t.Errorf("Expected %q, got %q", expect, out["outer"])
|
|
}
|
|
|
|
}
|
|
|
|
func TestRenderNestedValues(t *testing.T) {
|
|
e := New()
|
|
|
|
innerpath := "templates/inner.tpl"
|
|
outerpath := "templates/outer.tpl"
|
|
// Ensure namespacing rules are working.
|
|
deepestpath := "templates/inner.tpl"
|
|
checkrelease := "templates/release.tpl"
|
|
|
|
deepest := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "deepest"},
|
|
Templates: []*chart.Template{
|
|
{Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)},
|
|
{Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)},
|
|
},
|
|
Values: &chart.Config{Raw: `what: "milkshake"`},
|
|
}
|
|
|
|
inner := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "herrick"},
|
|
Templates: []*chart.Template{
|
|
{Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)},
|
|
},
|
|
Values: &chart.Config{Raw: `who: "Robert"`},
|
|
Dependencies: []*chart.Chart{deepest},
|
|
}
|
|
|
|
outer := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "top"},
|
|
Templates: []*chart.Template{
|
|
{Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)},
|
|
},
|
|
Values: &chart.Config{
|
|
Raw: `
|
|
what: stinkweed
|
|
who: me
|
|
herrick:
|
|
who: time`,
|
|
},
|
|
Dependencies: []*chart.Chart{inner},
|
|
}
|
|
|
|
injValues := chart.Config{
|
|
Raw: `
|
|
what: rosebuds
|
|
herrick:
|
|
deepest:
|
|
what: flower
|
|
global:
|
|
when: to-day`,
|
|
}
|
|
|
|
tmp, err := chartutil.CoalesceValues(outer, &injValues)
|
|
if err != nil {
|
|
t.Fatalf("Failed to coalesce values: %s", err)
|
|
}
|
|
|
|
inject := chartutil.Values{
|
|
"Values": tmp,
|
|
"Chart": outer.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "dyin",
|
|
},
|
|
}
|
|
|
|
t.Logf("Calculated values: %v", inject)
|
|
|
|
out, err := e.Render(outer, inject)
|
|
if err != nil {
|
|
t.Fatalf("failed to render templates: %s", err)
|
|
}
|
|
|
|
fullouterpath := "top/" + outerpath
|
|
if out[fullouterpath] != "Gather ye rosebuds while ye may" {
|
|
t.Errorf("Unexpected outer: %q", out[fullouterpath])
|
|
}
|
|
|
|
fullinnerpath := "top/charts/herrick/" + innerpath
|
|
if out[fullinnerpath] != "Old time is still a-flyin'" {
|
|
t.Errorf("Unexpected inner: %q", out[fullinnerpath])
|
|
}
|
|
|
|
fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath
|
|
if out[fulldeepestpath] != "And this same flower that smiles to-day" {
|
|
t.Errorf("Unexpected deepest: %q", out[fulldeepestpath])
|
|
}
|
|
|
|
fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease
|
|
if out[fullcheckrelease] != "Tomorrow will be dyin" {
|
|
t.Errorf("Unexpected release: %q", out[fullcheckrelease])
|
|
}
|
|
}
|
|
|
|
func TestRenderBuiltinValues(t *testing.T) {
|
|
inner := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "Latium"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
|
|
{Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
Files: []*any.Any{
|
|
{TypeUrl: "author", Value: []byte("Virgil")},
|
|
{TypeUrl: "book/title.txt", Value: []byte("Aeneid")},
|
|
},
|
|
}
|
|
|
|
outer := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "Troy"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{inner},
|
|
}
|
|
|
|
inject := chartutil.Values{
|
|
"Values": &chart.Config{Raw: ""},
|
|
"Chart": outer.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "Aeneid",
|
|
},
|
|
}
|
|
|
|
t.Logf("Calculated values: %v", outer)
|
|
|
|
out, err := New().Render(outer, inject)
|
|
if err != nil {
|
|
t.Fatalf("failed to render templates: %s", err)
|
|
}
|
|
|
|
expects := map[string]string{
|
|
"Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid",
|
|
"Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid",
|
|
"Troy/charts/Latium/templates/From": "Virgil Aeneid",
|
|
}
|
|
for file, expect := range expects {
|
|
if out[file] != expect {
|
|
t.Errorf("Expected %q, got %q", expect, out[file])
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestAlterFuncMap(t *testing.T) {
|
|
c := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "conrad"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)},
|
|
{Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
}
|
|
|
|
v := chartutil.Values{
|
|
"Values": &chart.Config{Raw: ""},
|
|
"Chart": c.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "Mistah Kurtz",
|
|
},
|
|
}
|
|
|
|
out, err := New().Render(c, v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expect := " Mistah Kurtz - he dead."
|
|
if got := out["conrad/templates/quote"]; got != expect {
|
|
t.Errorf("Expected %q, got %q (%v)", expect, got, out)
|
|
}
|
|
|
|
reqChart := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "conan"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)},
|
|
{Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
}
|
|
|
|
reqValues := chartutil.Values{
|
|
"Values": chartutil.Values{
|
|
"who": "us",
|
|
"bases": 2,
|
|
},
|
|
"Chart": reqChart.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "That 90s meme",
|
|
},
|
|
}
|
|
|
|
outReq, err := New().Render(reqChart, reqValues)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expectStr := "All your base are belong to us"
|
|
if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr {
|
|
t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq)
|
|
}
|
|
expectNum := "All 2 of them!"
|
|
if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum {
|
|
t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq)
|
|
}
|
|
|
|
// test required without passing in needed values with lint mode on
|
|
// verifies lint replaces required with an empty string (should not fail)
|
|
lintValues := chartutil.Values{
|
|
"Values": chartutil.Values{
|
|
"who": "us",
|
|
},
|
|
"Chart": reqChart.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "That 90s meme",
|
|
},
|
|
}
|
|
e := New()
|
|
e.LintMode = true
|
|
outReq, err = e.Render(reqChart, lintValues)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expectStr = "All your base are belong to us"
|
|
if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr {
|
|
t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq)
|
|
}
|
|
expectNum = "All of them!"
|
|
if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum {
|
|
t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq)
|
|
}
|
|
|
|
tplChart := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "TplFunction"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
}
|
|
|
|
tplValues := chartutil.Values{
|
|
"Values": chartutil.Values{
|
|
"value": "myvalue",
|
|
},
|
|
"Chart": tplChart.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "TestRelease",
|
|
},
|
|
}
|
|
|
|
outTpl, err := New().Render(tplChart, tplValues)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectTplStr := "Evaluate tpl Value: myvalue"
|
|
if gotStrTpl := outTpl["TplFunction/templates/base"]; gotStrTpl != expectTplStr {
|
|
t.Errorf("Expected %q, got %q (%v)", expectTplStr, gotStrTpl, outTpl)
|
|
}
|
|
|
|
tplChartWithFunction := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "TplFunction"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
}
|
|
|
|
tplValuesWithFunction := chartutil.Values{
|
|
"Values": chartutil.Values{
|
|
"value": "myvalue",
|
|
},
|
|
"Chart": tplChartWithFunction.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "TestRelease",
|
|
},
|
|
}
|
|
|
|
outTplWithFunction, err := New().Render(tplChartWithFunction, tplValuesWithFunction)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectTplStrWithFunction := "Evaluate tpl Value: \"myvalue\""
|
|
if gotStrTplWithFunction := outTplWithFunction["TplFunction/templates/base"]; gotStrTplWithFunction != expectTplStrWithFunction {
|
|
t.Errorf("Expected %q, got %q (%v)", expectTplStrWithFunction, gotStrTplWithFunction, outTplWithFunction)
|
|
}
|
|
|
|
tplChartWithInclude := &chart.Chart{
|
|
Metadata: &chart.Metadata{Name: "TplFunction"},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)},
|
|
{Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)},
|
|
},
|
|
Values: &chart.Config{Raw: ``},
|
|
Dependencies: []*chart.Chart{},
|
|
}
|
|
tplValueWithInclude := chartutil.Values{
|
|
"Values": chartutil.Values{
|
|
"value": "myvalue",
|
|
},
|
|
"Chart": tplChartWithInclude.Metadata,
|
|
"Release": chartutil.Values{
|
|
"Name": "TestRelease",
|
|
},
|
|
}
|
|
|
|
outTplWithInclude, err := New().Render(tplChartWithInclude, tplValueWithInclude)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedTplStrWithInclude := "\"TplFunction/templates/base\""
|
|
if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude {
|
|
t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude)
|
|
}
|
|
|
|
}
|