github.com/koderover/helm@v2.17.0+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 "bytes" 21 "fmt" 22 "log" 23 "path" 24 "sort" 25 "strings" 26 "text/template" 27 28 "github.com/Masterminds/sprig" 29 "github.com/pkg/errors" 30 31 "k8s.io/helm/pkg/chartutil" 32 "k8s.io/helm/pkg/proto/hapi/chart" 33 ) 34 35 const recursionMaxNums = 1000 36 37 // Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. 38 type Engine struct { 39 // FuncMap contains the template functions that will be passed to each 40 // render call. This may only be modified before the first call to Render. 41 FuncMap template.FuncMap 42 // If strict is enabled, template rendering will fail if a template references 43 // a value that was not passed in. 44 Strict bool 45 // In LintMode, some 'required' template values may be missing, so don't fail 46 LintMode bool 47 } 48 49 // New creates a new Go template Engine instance. 50 // 51 // The FuncMap is initialized here. You may modify the FuncMap _prior to_ the 52 // first invocation of Render. 53 // 54 // The FuncMap sets all of the Sprig functions except for those that provide 55 // access to the underlying OS (env, expandenv). 56 func New() *Engine { 57 f := FuncMap() 58 return &Engine{ 59 FuncMap: f, 60 } 61 } 62 63 // FuncMap returns a mapping of all of the functions that Engine has. 64 // 65 // Because some functions are late-bound (e.g. contain context-sensitive 66 // data), the functions may not all perform identically outside of an 67 // Engine as they will inside of an Engine. 68 // 69 // Known late-bound functions: 70 // 71 // - "include": This is late-bound in Engine.Render(). The version 72 // included in the FuncMap is a placeholder. 73 // - "required": This is late-bound in Engine.Render(). The version 74 // included in the FuncMap is a placeholder. 75 // - "tpl": This is late-bound in Engine.Render(). The version 76 // included in the FuncMap is a placeholder. 77 func FuncMap() template.FuncMap { 78 f := sprig.TxtFuncMap() 79 delete(f, "env") 80 delete(f, "expandenv") 81 82 // Add some extra functionality 83 extra := template.FuncMap{ 84 "toToml": chartutil.ToToml, 85 "toYaml": chartutil.ToYaml, 86 "fromYaml": chartutil.FromYaml, 87 "toJson": chartutil.ToJson, 88 "fromJson": chartutil.FromJson, 89 90 // This is a placeholder for the "include" function, which is 91 // late-bound to a template. By declaring it here, we preserve the 92 // integrity of the linter. 93 "include": func(string, interface{}) string { return "not implemented" }, 94 "required": func(string, interface{}) interface{} { return "not implemented" }, 95 "tpl": func(string, interface{}) interface{} { return "not implemented" }, 96 } 97 98 for k, v := range extra { 99 f[k] = v 100 } 101 102 return f 103 } 104 105 // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. 106 // 107 // Render can be called repeatedly on the same engine. 108 // 109 // This will look in the chart's 'templates' data (e.g. the 'templates/' directory) 110 // and attempt to render the templates there using the values passed in. 111 // 112 // Values are scoped to their templates. A dependency template will not have 113 // access to the values set for its parent. If chart "foo" includes chart "bar", 114 // "bar" will not have access to the values for "foo". 115 // 116 // Values should be prepared with something like `chartutils.ReadValues`. 117 // 118 // Values are passed through the templates according to scope. If the top layer 119 // chart includes the chart foo, which includes the chart bar, the values map 120 // will be examined for a table called "foo". If "foo" is found in vals, 121 // that section of the values will be passed into the "foo" chart. And if that 122 // section contains a value named "bar", that value will be passed on to the 123 // bar chart during render time. 124 func (e *Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { 125 // Render the charts 126 tmap := allTemplates(chrt, values) 127 return e.render(tmap) 128 } 129 130 // renderable is an object that can be rendered. 131 type renderable struct { 132 // tpl is the current template. 133 tpl string 134 // vals are the values to be supplied to the template. 135 vals chartutil.Values 136 // basePath namespace prefix to the templates of the current chart 137 basePath string 138 } 139 140 // alterFuncMap takes the Engine's FuncMap and adds context-specific functions. 141 // 142 // The resulting FuncMap is only valid for the passed-in template. 143 func (e *Engine) alterFuncMap(t *template.Template, referenceTpls map[string]renderable) template.FuncMap { 144 // Clone the func map because we are adding context-specific functions. 145 var funcMap template.FuncMap = map[string]interface{}{} 146 for k, v := range e.FuncMap { 147 funcMap[k] = v 148 } 149 150 includedNames := make(map[string]int) 151 152 // Add the 'include' function here so we can close over t. 153 funcMap["include"] = func(name string, data interface{}) (string, error) { 154 buf := bytes.NewBuffer(nil) 155 if v, ok := includedNames[name]; ok { 156 if v > recursionMaxNums { 157 return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name) 158 } 159 includedNames[name]++ 160 } else { 161 includedNames[name] = 1 162 } 163 if err := t.ExecuteTemplate(buf, name, data); err != nil { 164 return "", err 165 } 166 includedNames[name]-- 167 return buf.String(), nil 168 } 169 170 // Add the 'required' function here 171 funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { 172 if val == nil { 173 if e.LintMode { 174 // Don't fail on missing required values when linting 175 log.Printf("[INFO] Missing required value: %s", warn) 176 return "", nil 177 } 178 // Convert nil to "" in case required is piped into other functions 179 return "", fmt.Errorf(warn) 180 } else if _, ok := val.(string); ok { 181 if val == "" { 182 if e.LintMode { 183 // Don't fail on missing required values when linting 184 log.Printf("[INFO] Missing required value: %s", warn) 185 return val, nil 186 } 187 return val, fmt.Errorf(warn) 188 } 189 } 190 return val, nil 191 } 192 193 // Add the 'tpl' function here 194 funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { 195 basePath, err := vals.PathValue("Template.BasePath") 196 if err != nil { 197 return "", fmt.Errorf("Cannot retrieve Template.Basepath from values inside tpl function: %s (%s)", tpl, err.Error()) 198 } 199 200 r := renderable{ 201 tpl: tpl, 202 vals: vals, 203 basePath: basePath.(string), 204 } 205 206 templates := map[string]renderable{} 207 templateName, err := vals.PathValue("Template.Name") 208 if err != nil { 209 return "", fmt.Errorf("Cannot retrieve Template.Name from values inside tpl function: %s (%s)", tpl, err.Error()) 210 } 211 212 templates[templateName.(string)] = r 213 214 result, err := e.renderWithReferences(templates, referenceTpls) 215 if err != nil { 216 return "", fmt.Errorf("Error during tpl function execution for %q: %s", tpl, err.Error()) 217 } 218 return result[templateName.(string)], nil 219 } 220 221 return funcMap 222 } 223 224 // render takes a map of templates/values and renders them. 225 func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { 226 return e.renderWithReferences(tpls, tpls) 227 } 228 229 // renderWithReferences takes a map of templates/values to render, and a map of 230 // templates which can be referenced within them. 231 func (e *Engine) renderWithReferences(tpls map[string]renderable, referenceTpls map[string]renderable) (rendered map[string]string, err error) { 232 // Basically, what we do here is start with an empty parent template and then 233 // build up a list of templates -- one for each file. Once all of the templates 234 // have been parsed, we loop through again and execute every template. 235 // 236 // The idea with this process is to make it possible for more complex templates 237 // to share common blocks, but to make the entire thing feel like a file-based 238 // template engine. 239 defer func() { 240 if r := recover(); r != nil { 241 err = fmt.Errorf("rendering template failed: %v", r) 242 } 243 }() 244 t := template.New("gotpl") 245 if e.Strict { 246 t.Option("missingkey=error") 247 } else { 248 // Not that zero will attempt to add default values for types it knows, 249 // but will still emit <no value> for others. We mitigate that later. 250 t.Option("missingkey=zero") 251 } 252 253 funcMap := e.alterFuncMap(t, referenceTpls) 254 255 // We want to parse the templates in a predictable order. The order favors 256 // higher-level (in file system) templates over deeply nested templates. 257 keys := sortTemplates(tpls) 258 259 files := []string{} 260 261 for _, fname := range keys { 262 r := tpls[fname] 263 t = t.New(fname).Funcs(funcMap) 264 if _, err := t.Parse(r.tpl); err != nil { 265 return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) 266 } 267 files = append(files, fname) 268 } 269 270 // Adding the reference templates to the template context 271 // so they can be referenced in the tpl function 272 for fname, r := range referenceTpls { 273 if t.Lookup(fname) == nil { 274 t = t.New(fname).Funcs(funcMap) 275 if _, err := t.Parse(r.tpl); err != nil { 276 return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) 277 } 278 } 279 } 280 281 rendered = make(map[string]string, len(files)) 282 var buf bytes.Buffer 283 for _, file := range files { 284 // Don't render partials. We don't care about the direct output of partials. 285 // They are only included from other templates. 286 if strings.HasPrefix(path.Base(file), "_") { 287 continue 288 } 289 // At render time, add information about the template that is being rendered. 290 vals := tpls[file].vals 291 vals["Template"] = map[string]interface{}{"Name": file, "BasePath": tpls[file].basePath} 292 if err := t.ExecuteTemplate(&buf, file, vals); err != nil { 293 return map[string]string{}, fmt.Errorf("render error in %q: %s", file, err) 294 } 295 296 // Work around the issue where Go will emit "<no value>" even if Options(missing=zero) 297 // is set. Since missing=error will never get here, we do not need to handle 298 // the Strict case. 299 rendered[file] = strings.Replace(buf.String(), "<no value>", "", -1) 300 buf.Reset() 301 } 302 303 return rendered, nil 304 } 305 306 func sortTemplates(tpls map[string]renderable) []string { 307 keys := make([]string, len(tpls)) 308 i := 0 309 for key := range tpls { 310 keys[i] = key 311 i++ 312 } 313 sort.Sort(sort.Reverse(byPathLen(keys))) 314 return keys 315 } 316 317 type byPathLen []string 318 319 func (p byPathLen) Len() int { return len(p) } 320 func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] } 321 func (p byPathLen) Less(i, j int) bool { 322 a, b := p[i], p[j] 323 ca, cb := strings.Count(a, "/"), strings.Count(b, "/") 324 if ca == cb { 325 return strings.Compare(a, b) == -1 326 } 327 return ca < cb 328 } 329 330 // allTemplates returns all templates for a chart and its dependencies. 331 // 332 // As it goes, it also prepares the values in a scope-sensitive manner. 333 func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { 334 templates := map[string]renderable{} 335 recAllTpls(c, templates, vals, true, "") 336 return templates 337 } 338 339 // recAllTpls recurses through the templates in a chart. 340 // 341 // As it recurses, it also sets the values to be appropriate for the template 342 // scope. 343 func recAllTpls(c *chart.Chart, templates map[string]renderable, parentVals chartutil.Values, top bool, parentID string) { 344 // This should never evaluate to a nil map. That will cause problems when 345 // values are appended later. 346 cvals := chartutil.Values{} 347 if top { 348 // If this is the top of the rendering tree, assume that parentVals 349 // is already resolved to the authoritative values. 350 cvals = parentVals 351 } else if c.Metadata != nil && c.Metadata.Name != "" { 352 // If there is a {{.Values.ThisChart}} in the parent metadata, 353 // copy that into the {{.Values}} for this template. 354 newVals := chartutil.Values{} 355 if vs, err := parentVals.Table("Values"); err == nil { 356 if tmp, err := vs.Table(c.Metadata.Name); err == nil { 357 newVals = tmp 358 } 359 } 360 361 cvals = map[string]interface{}{ 362 "Values": newVals, 363 "Release": parentVals["Release"], 364 "Chart": c.Metadata, 365 "Files": chartutil.NewFiles(c.Files), 366 "Capabilities": parentVals["Capabilities"], 367 } 368 } 369 370 newParentID := c.Metadata.Name 371 if parentID != "" { 372 // We artificially reconstruct the chart path to child templates. This 373 // creates a namespaced filename that can be used to track down the source 374 // of a particular template declaration. 375 newParentID = path.Join(parentID, "charts", newParentID) 376 } 377 378 for _, child := range c.Dependencies { 379 recAllTpls(child, templates, cvals, false, newParentID) 380 } 381 for _, t := range c.Templates { 382 templates[path.Join(newParentID, t.Name)] = renderable{ 383 tpl: string(t.Data), 384 vals: cvals, 385 basePath: path.Join(newParentID, "templates"), 386 } 387 } 388 }