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