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  }