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