fix(engine): make template rendering thread safe

See https://github.com/helm/helm/pull/4828

Signed-off-by: Adam Reese <adam@reese.io>
pull/5400/head
Adam Reese 6 years ago
parent f501d4c960
commit d841a1b1d9
No known key found for this signature in database
GPG Key ID: 06F35E60A7A18DD6

@ -36,8 +36,7 @@ type Engine struct {
funcMap template.FuncMap funcMap template.FuncMap
// If strict is enabled, template rendering will fail if a template references // If strict is enabled, template rendering will fail if a template references
// a value that was not passed in. // a value that was not passed in.
Strict bool Strict bool
currentTemplates map[string]renderable
} }
// New creates a new Go template Engine instance. // New creates a new Go template Engine instance.
@ -115,8 +114,7 @@ func FuncMap() template.FuncMap {
func (e *Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { func (e *Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) {
// Render the charts // Render the charts
tmap := allTemplates(chrt, values) tmap := allTemplates(chrt, values)
e.currentTemplates = tmap return e.render(tmap)
return e.render(chrt, tmap)
} }
// renderable is an object that can be rendered. // renderable is an object that can be rendered.
@ -132,7 +130,7 @@ type renderable struct {
// alterFuncMap takes the Engine's FuncMap and adds context-specific functions. // alterFuncMap takes the Engine's FuncMap and adds context-specific functions.
// //
// The resulting FuncMap is only valid for the passed-in template. // The resulting FuncMap is only valid for the passed-in template.
func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { func (e *Engine) alterFuncMap(t *template.Template, referenceTpls map[string]renderable) template.FuncMap {
// Clone the func map because we are adding context-specific functions. // Clone the func map because we are adding context-specific functions.
funcMap := make(template.FuncMap) funcMap := make(template.FuncMap)
for k, v := range e.funcMap { for k, v := range e.funcMap {
@ -179,7 +177,7 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
templates := make(map[string]renderable) templates := make(map[string]renderable)
templates[templateName.(string)] = r templates[templateName.(string)] = r
result, err := e.render(nil, templates) result, err := e.renderWithReferences(templates, referenceTpls)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl)
} }
@ -190,7 +188,14 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
} }
// render takes a map of templates/values and renders them. // render takes a map of templates/values and renders them.
func (e *Engine) render(ch *chart.Chart, tpls map[string]renderable) (rendered map[string]string, err error) { func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) {
return e.renderWithReferences(tpls, tpls)
}
// renderWithReferences takes a map of templates/values to render, and a map of
// templates which can be referenced within them.
func (e *Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) {
// Basically, what we do here is start with an empty parent template and then // Basically, what we do here is start with an empty parent template and then
// build up a list of templates -- one for each file. Once all of the templates // build up a list of templates -- one for each file. Once all of the templates
// have been parsed, we loop through again and execute every template. // have been parsed, we loop through again and execute every template.
@ -212,7 +217,7 @@ func (e *Engine) render(ch *chart.Chart, tpls map[string]renderable) (rendered m
t.Option("missingkey=zero") t.Option("missingkey=zero")
} }
funcMap := e.alterFuncMap(t) funcMap := e.alterFuncMap(t, referenceTpls)
// We want to parse the templates in a predictable order. The order favors // We want to parse the templates in a predictable order. The order favors
// higher-level (in file system) templates over deeply nested templates. // higher-level (in file system) templates over deeply nested templates.
@ -228,9 +233,9 @@ func (e *Engine) render(ch *chart.Chart, tpls map[string]renderable) (rendered m
files = append(files, fname) files = append(files, fname)
} }
// Adding the engine's currentTemplates to the template context // Adding the reference templates to the template context
// so they can be referenced in the tpl function // so they can be referenced in the tpl function
for fname, r := range e.currentTemplates { for fname, r := range referenceTpls {
if t.Lookup(fname) == nil { if t.Lookup(fname) == nil {
if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil { if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil {
return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname) return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname)
@ -261,9 +266,9 @@ func (e *Engine) render(ch *chart.Chart, tpls map[string]renderable) (rendered m
Data: []byte(strings.Replace(buf.String(), "<no value>", "", -1)), Data: []byte(strings.Replace(buf.String(), "<no value>", "", -1)),
} }
rendered[file] = string(f.Data) rendered[file] = string(f.Data)
if ch != nil { // if ch != nil {
ch.Files = append(ch.Files, f) // ch.Files = append(ch.Files, f)
} // }
} }
return rendered, nil return rendered, nil

@ -150,7 +150,7 @@ func TestRenderInternals(t *testing.T) {
"three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals},
} }
out, err := e.render(nil, tpls) out, err := e.render(tpls)
if err != nil { if err != nil {
t.Fatalf("Failed template rendering: %s", err) t.Fatalf("Failed template rendering: %s", err)
} }
@ -183,7 +183,7 @@ func TestParallelRenderInternals(t *testing.T) {
tt := fmt.Sprintf("expect-%d", i) tt := fmt.Sprintf("expect-%d", i)
v := chartutil.Values{"val": tt} v := chartutil.Values{"val": tt}
tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}}
out, err := e.render(nil, tpls) out, err := e.render(tpls)
if err != nil { if err != nil {
t.Errorf("Failed to render %s: %s", tt, err) t.Errorf("Failed to render %s: %s", tt, err)
} }

Loading…
Cancel
Save