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