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  }