Skip to content

Commit 8937c77

Browse files
committed
fix(2452): sort templates before parse
This sorts templates by depth before sending them to the template parser. Deepest templates are parsed first, with umbrella templates parsed last. Since template definition names are LIFO, that means that the highest level templates will claim the namespace. Or, to put it simply, you can predictably override a child's defined template by re-defining it in a parent chart. Closes helm#2452
1 parent 7a49e5c commit 8937c77

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

pkg/engine/engine.go

+32-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"fmt"
2222
"path"
23+
"sort"
2324
"strings"
2425
"text/template"
2526

@@ -181,8 +182,14 @@ func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) {
181182

182183
funcMap := e.alterFuncMap(t)
183184

185+
// We want to parse the templates in a predictable order. The order favors
186+
// higher-level (in file system) templates over deeply nested templates.
187+
keys := sortTemplates(tpls)
188+
184189
files := []string{}
185-
for fname, r := range tpls {
190+
//for fname, r := range tpls {
191+
for _, fname := range keys {
192+
r := tpls[fname]
186193
t = t.New(fname).Funcs(funcMap)
187194
if _, err := t.Parse(r.tpl); err != nil {
188195
return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err)
@@ -215,6 +222,30 @@ func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) {
215222
return rendered, nil
216223
}
217224

225+
func sortTemplates(tpls map[string]renderable) []string {
226+
keys := make([]string, len(tpls))
227+
i := 0
228+
for key := range tpls {
229+
keys[i] = key
230+
i++
231+
}
232+
sort.Sort(sort.Reverse(byPathLen(keys)))
233+
return keys
234+
}
235+
236+
type byPathLen []string
237+
238+
func (p byPathLen) Len() int { return len(p) }
239+
func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] }
240+
func (p byPathLen) Less(i, j int) bool {
241+
a, b := p[i], p[j]
242+
ca, cb := strings.Count(a, "/"), strings.Count(b, "/")
243+
if ca == cb {
244+
return strings.Compare(a, b) == -1
245+
}
246+
return ca < cb
247+
}
248+
218249
// allTemplates returns all templates for a chart and its dependencies.
219250
//
220251
// As it goes, it also prepares the values in a scope-sensitive manner.

pkg/engine/engine_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,37 @@ import (
2727
"github.com/golang/protobuf/ptypes/any"
2828
)
2929

30+
func TestSortTemplates(t *testing.T) {
31+
tpls := map[string]renderable{
32+
"/mychart/templates/foo.tpl": {},
33+
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {},
34+
"/mychart/templates/bar.tpl": {},
35+
"/mychart/templates/charts/foo/templates/bar.tpl": {},
36+
"/mychart/templates/_foo.tpl": {},
37+
"/mychart/templates/charts/foo/templates/foo.tpl": {},
38+
"/mychart/templates/charts/bar/templates/foo.tpl": {},
39+
}
40+
got := sortTemplates(tpls)
41+
if len(got) != len(tpls) {
42+
t.Fatal("Sorted results are missing templates")
43+
}
44+
45+
expect := []string{
46+
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl",
47+
"/mychart/templates/charts/foo/templates/foo.tpl",
48+
"/mychart/templates/charts/foo/templates/bar.tpl",
49+
"/mychart/templates/charts/bar/templates/foo.tpl",
50+
"/mychart/templates/foo.tpl",
51+
"/mychart/templates/bar.tpl",
52+
"/mychart/templates/_foo.tpl",
53+
}
54+
for i, e := range expect {
55+
if got[i] != e {
56+
t.Errorf("expected %q, got %q at index %d\n\tExp: %#v\n\tGot: %#v", e, got[i], i, expect, got)
57+
}
58+
}
59+
}
60+
3061
func TestEngine(t *testing.T) {
3162
e := New()
3263

0 commit comments

Comments
 (0)