github.com/umeshredd/helm@v3.0.0-alpha.1+incompatible/pkg/engine/engine.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 "path" 22 "sort" 23 "strings" 24 "text/template" 25 26 "github.com/pkg/errors" 27 28 "helm.sh/helm/pkg/chart" 29 "helm.sh/helm/pkg/chartutil" 30 ) 31 32 // Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. 33 type Engine struct { 34 // If strict is enabled, template rendering will fail if a template references 35 // a value that was not passed in. 36 Strict bool 37 } 38 39 // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. 40 // 41 // Render can be called repeatedly on the same engine. 42 // 43 // This will look in the chart's 'templates' data (e.g. the 'templates/' directory) 44 // and attempt to render the templates there using the values passed in. 45 // 46 // Values are scoped to their templates. A dependency template will not have 47 // access to the values set for its parent. If chart "foo" includes chart "bar", 48 // "bar" will not have access to the values for "foo". 49 // 50 // Values should be prepared with something like `chartutils.ReadValues`. 51 // 52 // Values are passed through the templates according to scope. If the top layer 53 // chart includes the chart foo, which includes the chart bar, the values map 54 // will be examined for a table called "foo". If "foo" is found in vals, 55 // that section of the values will be passed into the "foo" chart. And if that 56 // section contains a value named "bar", that value will be passed on to the 57 // bar chart during render time. 58 func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { 59 tmap := allTemplates(chrt, values) 60 return e.render(tmap) 61 } 62 63 // Render takes a chart, optional values, and value overrides, and attempts to 64 // render the Go templates using the default options. 65 func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { 66 return new(Engine).Render(chrt, values) 67 } 68 69 // renderable is an object that can be rendered. 70 type renderable struct { 71 // tpl is the current template. 72 tpl string 73 // vals are the values to be supplied to the template. 74 vals chartutil.Values 75 // namespace prefix to the templates of the current chart 76 basePath string 77 } 78 79 // initFunMap creates the Engine's FuncMap and adds context-specific functions. 80 func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { 81 funcMap := funcMap() 82 83 // Add the 'include' function here so we can close over t. 84 funcMap["include"] = func(name string, data interface{}) (string, error) { 85 var buf strings.Builder 86 err := t.ExecuteTemplate(&buf, name, data) 87 return buf.String(), err 88 } 89 90 // Add the 'tpl' function here 91 funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { 92 basePath, err := vals.PathValue("Template.BasePath") 93 if err != nil { 94 return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl) 95 } 96 97 templateName, err := vals.PathValue("Template.Name") 98 if err != nil { 99 return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) 100 } 101 102 templates := map[string]renderable{ 103 templateName.(string): { 104 tpl: tpl, 105 vals: vals, 106 basePath: basePath.(string), 107 }, 108 } 109 110 result, err := e.renderWithReferences(templates, referenceTpls) 111 if err != nil { 112 return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) 113 } 114 return result[templateName.(string)], nil 115 } 116 t.Funcs(funcMap) 117 } 118 119 // render takes a map of templates/values and renders them. 120 func (e Engine) render(tpls map[string]renderable) (map[string]string, error) { 121 return e.renderWithReferences(tpls, tpls) 122 } 123 124 // renderWithReferences takes a map of templates/values to render, and a map of 125 // templates which can be referenced within them. 126 func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) { 127 // Basically, what we do here is start with an empty parent template and then 128 // build up a list of templates -- one for each file. Once all of the templates 129 // have been parsed, we loop through again and execute every template. 130 // 131 // The idea with this process is to make it possible for more complex templates 132 // to share common blocks, but to make the entire thing feel like a file-based 133 // template engine. 134 defer func() { 135 if r := recover(); r != nil { 136 err = errors.Errorf("rendering template failed: %v", r) 137 } 138 }() 139 t := template.New("gotpl") 140 if e.Strict { 141 t.Option("missingkey=error") 142 } else { 143 // Not that zero will attempt to add default values for types it knows, 144 // but will still emit <no value> for others. We mitigate that later. 145 t.Option("missingkey=zero") 146 } 147 148 e.initFunMap(t, referenceTpls) 149 150 // We want to parse the templates in a predictable order. The order favors 151 // higher-level (in file system) templates over deeply nested templates. 152 keys := sortTemplates(tpls) 153 154 for _, filename := range keys { 155 r := tpls[filename] 156 if _, err := t.New(filename).Parse(r.tpl); err != nil { 157 return map[string]string{}, parseTemplateError(filename, err) 158 } 159 } 160 161 // Adding the reference templates to the template context 162 // so they can be referenced in the tpl function 163 for filename, r := range referenceTpls { 164 if t.Lookup(filename) == nil { 165 if _, err := t.New(filename).Parse(r.tpl); err != nil { 166 return map[string]string{}, parseTemplateError(filename, err) 167 } 168 } 169 } 170 171 rendered = make(map[string]string, len(keys)) 172 for _, filename := range keys { 173 // Don't render partials. We don't care out the direct output of partials. 174 // They are only included from other templates. 175 if strings.HasPrefix(path.Base(filename), "_") { 176 continue 177 } 178 // At render time, add information about the template that is being rendered. 179 vals := tpls[filename].vals 180 vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} 181 var buf strings.Builder 182 if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { 183 return map[string]string{}, parseTemplateError(filename, err) 184 } 185 186 // Work around the issue where Go will emit "<no value>" even if Options(missing=zero) 187 // is set. Since missing=error will never get here, we do not need to handle 188 // the Strict case. 189 f := &chart.File{ 190 Name: strings.ReplaceAll(filename, "/templates", "/manifests"), 191 Data: []byte(strings.ReplaceAll(buf.String(), "<no value>", "")), 192 } 193 rendered[filename] = string(f.Data) 194 } 195 196 return rendered, nil 197 } 198 199 func parseTemplateError(filename string, err error) error { 200 tokens := strings.Split(err.Error(), ": ") 201 if len(tokens) == 1 { 202 // This might happen if a non-templating error occurs 203 return fmt.Errorf("render error in (%s): %s", filename, err) 204 } 205 // The first token is "template" 206 // The second token is either "filename:lineno" or "filename:lineNo:columnNo" 207 location := tokens[1] 208 // The remaining tokens make up a stacktrace-like chain, ending with the relevant error 209 errMsg := tokens[len(tokens)-1] 210 return fmt.Errorf("render error at (%s): %s", string(location), errMsg) 211 } 212 213 func sortTemplates(tpls map[string]renderable) []string { 214 keys := make([]string, len(tpls)) 215 i := 0 216 for key := range tpls { 217 keys[i] = key 218 i++ 219 } 220 sort.Sort(sort.Reverse(byPathLen(keys))) 221 return keys 222 } 223 224 type byPathLen []string 225 226 func (p byPathLen) Len() int { return len(p) } 227 func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] } 228 func (p byPathLen) Less(i, j int) bool { 229 a, b := p[i], p[j] 230 ca, cb := strings.Count(a, "/"), strings.Count(b, "/") 231 if ca == cb { 232 return strings.Compare(a, b) == -1 233 } 234 return ca < cb 235 } 236 237 // allTemplates returns all templates for a chart and its dependencies. 238 // 239 // As it goes, it also prepares the values in a scope-sensitive manner. 240 func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { 241 templates := make(map[string]renderable) 242 recAllTpls(c, templates, vals) 243 return templates 244 } 245 246 // recAllTpls recurses through the templates in a chart. 247 // 248 // As it recurses, it also sets the values to be appropriate for the template 249 // scope. 250 func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) { 251 next := map[string]interface{}{ 252 "Chart": c.Metadata, 253 "Files": newFiles(c.Files), 254 "Release": vals["Release"], 255 "Capabilities": vals["Capabilities"], 256 "Values": make(chartutil.Values), 257 } 258 259 // If there is a {{.Values.ThisChart}} in the parent metadata, 260 // copy that into the {{.Values}} for this template. 261 if c.IsRoot() { 262 next["Values"] = vals["Values"] 263 } else if vs, err := vals.Table("Values." + c.Name()); err == nil { 264 next["Values"] = vs 265 } 266 267 for _, child := range c.Dependencies() { 268 recAllTpls(child, templates, next) 269 } 270 271 isLibChart := chartutil.IsLibraryChart(c) 272 newParentID := c.ChartFullPath() 273 for _, t := range c.Templates { 274 if !chartutil.IsTemplateValid(t.Name, isLibChart) { 275 continue 276 } 277 templates[path.Join(newParentID, t.Name)] = renderable{ 278 tpl: string(t.Data), 279 vals: next, 280 basePath: path.Join(newParentID, "templates"), 281 } 282 } 283 }