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  }