github.com/koderover/helm@v2.17.0+incompatible/pkg/chartutil/values.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 chartutil
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"log"
    25  	"strings"
    26  
    27  	"github.com/ghodss/yaml"
    28  	"github.com/golang/protobuf/ptypes/timestamp"
    29  	"k8s.io/helm/pkg/proto/hapi/chart"
    30  )
    31  
    32  // ErrNoTable indicates that a chart does not have a matching table.
    33  type ErrNoTable error
    34  
    35  // ErrNoValue indicates that Values does not contain a key with a value
    36  type ErrNoValue error
    37  
    38  // GlobalKey is the name of the Values key that is used for storing global vars.
    39  const GlobalKey = "global"
    40  
    41  // Values represents a collection of chart values.
    42  type Values map[string]interface{}
    43  
    44  // YAML encodes the Values into a YAML string.
    45  func (v Values) YAML() (string, error) {
    46  	b, err := yaml.Marshal(v)
    47  	return string(b), err
    48  }
    49  
    50  // Table gets a table (YAML subsection) from a Values object.
    51  //
    52  // The table is returned as a Values.
    53  //
    54  // Compound table names may be specified with dots:
    55  //
    56  //	foo.bar
    57  //
    58  // The above will be evaluated as "The table bar inside the table
    59  // foo".
    60  //
    61  // An ErrNoTable is returned if the table does not exist.
    62  func (v Values) Table(name string) (Values, error) {
    63  	names := strings.Split(name, ".")
    64  	table := v
    65  	var err error
    66  
    67  	for _, n := range names {
    68  		table, err = tableLookup(table, n)
    69  		if err != nil {
    70  			return table, err
    71  		}
    72  	}
    73  	return table, err
    74  }
    75  
    76  // AsMap is a utility function for converting Values to a map[string]interface{}.
    77  //
    78  // It protects against nil map panics.
    79  func (v Values) AsMap() map[string]interface{} {
    80  	if v == nil || len(v) == 0 {
    81  		return map[string]interface{}{}
    82  	}
    83  	return v
    84  }
    85  
    86  // Encode writes serialized Values information to the given io.Writer.
    87  func (v Values) Encode(w io.Writer) error {
    88  	//return yaml.NewEncoder(w).Encode(v)
    89  	out, err := yaml.Marshal(v)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	_, err = w.Write(out)
    94  	return err
    95  }
    96  
    97  // MergeInto takes the properties in src and merges them into Values. Maps
    98  // are merged while values and arrays are replaced.
    99  func (v Values) MergeInto(src Values) {
   100  	for key, srcVal := range src {
   101  		destVal, found := v[key]
   102  
   103  		if found && istable(srcVal) && istable(destVal) {
   104  			destMap := destVal.(map[string]interface{})
   105  			srcMap := srcVal.(map[string]interface{})
   106  			Values(destMap).MergeInto(Values(srcMap))
   107  		} else {
   108  			v[key] = srcVal
   109  		}
   110  	}
   111  }
   112  
   113  func tableLookup(v Values, simple string) (Values, error) {
   114  	v2, ok := v[simple]
   115  	if !ok {
   116  		return v, ErrNoTable(fmt.Errorf("no table named %q (%v)", simple, v))
   117  	}
   118  	if vv, ok := v2.(map[string]interface{}); ok {
   119  		return vv, nil
   120  	}
   121  
   122  	// This catches a case where a value is of type Values, but doesn't (for some
   123  	// reason) match the map[string]interface{}. This has been observed in the
   124  	// wild, and might be a result of a nil map of type Values.
   125  	if vv, ok := v2.(Values); ok {
   126  		return vv, nil
   127  	}
   128  
   129  	var e ErrNoTable = fmt.Errorf("no table named %q", simple)
   130  	return map[string]interface{}{}, e
   131  }
   132  
   133  // ReadValues will parse YAML byte data into a Values.
   134  func ReadValues(data []byte) (vals Values, err error) {
   135  	err = yaml.Unmarshal(data, &vals)
   136  	if len(vals) == 0 {
   137  		vals = Values{}
   138  	}
   139  	return
   140  }
   141  
   142  // ReadValuesFile will parse a YAML file into a map of values.
   143  func ReadValuesFile(filename string) (Values, error) {
   144  	data, err := ioutil.ReadFile(filename)
   145  	if err != nil {
   146  		return map[string]interface{}{}, err
   147  	}
   148  	return ReadValues(data)
   149  }
   150  
   151  // CoalesceValues coalesces all of the values in a chart (and its subcharts).
   152  //
   153  // Values are coalesced together using the following rules:
   154  //
   155  //	- Values in a higher level chart always override values in a lower-level
   156  //		dependency chart
   157  //	- Scalar values and arrays are replaced, maps are merged
   158  //	- A chart has access to all of the variables for it, as well as all of
   159  //		the values destined for its dependencies.
   160  func CoalesceValues(chrt *chart.Chart, vals *chart.Config) (Values, error) {
   161  	cvals := Values{}
   162  	// Parse values if not nil. We merge these at the top level because
   163  	// the passed-in values are in the same namespace as the parent chart.
   164  	if vals != nil {
   165  		evals, err := ReadValues([]byte(vals.Raw))
   166  		if err != nil {
   167  			return cvals, err
   168  		}
   169  		return coalesce(chrt, evals)
   170  	}
   171  
   172  	return coalesceDeps(chrt, cvals)
   173  }
   174  
   175  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
   176  //
   177  // This is a helper function for CoalesceValues.
   178  func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   179  	var err error
   180  	dest, err = coalesceValues(ch, dest)
   181  	if err != nil {
   182  		return dest, err
   183  	}
   184  	return coalesceDeps(ch, dest)
   185  }
   186  
   187  // coalesceDeps coalesces the dependencies of the given chart.
   188  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   189  	for _, subchart := range chrt.Dependencies {
   190  		if c, ok := dest[subchart.Metadata.Name]; !ok {
   191  			// If dest doesn't already have the key, create it.
   192  			dest[subchart.Metadata.Name] = map[string]interface{}{}
   193  		} else if !istable(c) {
   194  			return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c)
   195  		}
   196  		if dv, ok := dest[subchart.Metadata.Name]; ok {
   197  			dvmap := dv.(map[string]interface{})
   198  
   199  			// Get globals out of dest and merge them into dvmap.
   200  			dvmap = coalesceGlobals(dvmap, dest, chrt.Metadata.Name)
   201  
   202  			var err error
   203  			// Now coalesce the rest of the values.
   204  			dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap)
   205  			if err != nil {
   206  				return dest, err
   207  			}
   208  		}
   209  	}
   210  	return dest, nil
   211  }
   212  
   213  // coalesceGlobals copies the globals out of src and merges them into dest.
   214  //
   215  // For convenience, returns dest.
   216  func coalesceGlobals(dest, src map[string]interface{}, chartName string) map[string]interface{} {
   217  	var dg, sg map[string]interface{}
   218  
   219  	if destglob, ok := dest[GlobalKey]; !ok {
   220  		dg = map[string]interface{}{}
   221  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
   222  		log.Printf("Warning: Skipping globals for chart '%s' because destination '%s' is not a table.", chartName, GlobalKey)
   223  		return dg
   224  	}
   225  
   226  	if srcglob, ok := src[GlobalKey]; !ok {
   227  		sg = map[string]interface{}{}
   228  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
   229  		log.Printf("Warning: skipping globals for chart '%s' because source '%s' is not a table.", chartName, GlobalKey)
   230  		return dg
   231  	}
   232  
   233  	rv := make(map[string]interface{})
   234  	for k, v := range dest {
   235  		rv[k] = v
   236  	}
   237  
   238  	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
   239  	// reverses that decision. It may somehow be possible to introduce a loop
   240  	// here, but I haven't found a way. So for the time being, let's allow
   241  	// tables in globals.
   242  
   243  	// Basically, we reverse order of coalesce here to merge
   244  	// top-down.
   245  	rv[GlobalKey] = coalesceTables(sg, dg, chartName)
   246  	return rv
   247  }
   248  
   249  // coalesceValues builds up a values map for a particular chart.
   250  //
   251  // Values in v will override the values in the chart.
   252  func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) {
   253  	// If there are no values in the chart, we just return the given values
   254  	if c.Values == nil || c.Values.Raw == "" {
   255  		return v, nil
   256  	}
   257  
   258  	nv, err := ReadValues([]byte(c.Values.Raw))
   259  	if err != nil {
   260  		// On error, we return just the overridden values.
   261  		// FIXME: We should log this error. It indicates that the YAML data
   262  		// did not parse.
   263  		return v, fmt.Errorf("Error: Reading chart '%s' default values (%s): %s", c.Metadata.Name, c.Values.Raw, err)
   264  	}
   265  
   266  	return coalesceTables(v, nv.AsMap(), c.Metadata.Name), nil
   267  }
   268  
   269  // coalesceTables merges a source map into a destination map.
   270  //
   271  // dest is considered authoritative.
   272  func coalesceTables(dst, src map[string]interface{}, chartName string) map[string]interface{} {
   273  	// Because dest has higher precedence than src, dest values override src
   274  	// values.
   275  
   276  	rv := make(map[string]interface{})
   277  	for key, val := range src {
   278  		dv, ok := dst[key]
   279  		if !ok { // if not in dst, then copy from src
   280  			rv[key] = val
   281  			continue
   282  		}
   283  		if dv == nil { // if set to nil in dst, then ignore
   284  			// When the YAML value is null, we skip the value's key.
   285  			// This allows Helm's various sources of values (value files or --set) to
   286  			// remove incompatible keys from any previous chart, file, or set values.
   287  			continue
   288  		}
   289  
   290  		srcTable, srcIsTable := val.(map[string]interface{})
   291  		dstTable, dstIsTable := dv.(map[string]interface{})
   292  		switch {
   293  		case srcIsTable && dstIsTable: // both tables, we coalesce
   294  			rv[key] = coalesceTables(dstTable, srcTable, chartName)
   295  		case srcIsTable && !dstIsTable:
   296  			log.Printf("Warning: Merging destination map for chart '%s'. Overwriting table item '%s', with non table value: %v", chartName, key, dv)
   297  			rv[key] = dv
   298  		case !srcIsTable && dstIsTable:
   299  			log.Printf("Warning: Merging destination map for chart '%s'. The destination item '%s' is a table and ignoring the source '%s' as it has a non-table value of: %v", chartName, key, key, val)
   300  			rv[key] = dv
   301  		default: // neither are tables, simply take the dst value
   302  			rv[key] = dv
   303  		}
   304  	}
   305  
   306  	// do we have anything in dst that wasn't processed already that we need to copy across?
   307  	for key, val := range dst {
   308  		if val == nil {
   309  			continue
   310  		}
   311  		_, ok := rv[key]
   312  		if !ok {
   313  			rv[key] = val
   314  		}
   315  	}
   316  
   317  	return rv
   318  }
   319  
   320  // ReleaseOptions represents the additional release options needed
   321  // for the composition of the final values struct
   322  type ReleaseOptions struct {
   323  	Name      string
   324  	Time      *timestamp.Timestamp
   325  	Namespace string
   326  	IsUpgrade bool
   327  	IsInstall bool
   328  	Revision  int
   329  }
   330  
   331  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   332  //
   333  // WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will
   334  // remain in the codebase to stay SemVer compliant.
   335  //
   336  // In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter.
   337  func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
   338  	caps := &Capabilities{APIVersions: DefaultVersionSet}
   339  	return ToRenderValuesCaps(chrt, chrtVals, options, caps)
   340  }
   341  
   342  // ToRenderValuesCaps composes the struct from the data coming from the Releases, Charts and Values files
   343  //
   344  // This takes both ReleaseOptions and Capabilities to merge into the render values.
   345  func ToRenderValuesCaps(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions, caps *Capabilities) (Values, error) {
   346  
   347  	top := map[string]interface{}{
   348  		"Release": map[string]interface{}{
   349  			"Name":      options.Name,
   350  			"Time":      options.Time,
   351  			"Namespace": options.Namespace,
   352  			"IsUpgrade": options.IsUpgrade,
   353  			"IsInstall": options.IsInstall,
   354  			"Revision":  options.Revision,
   355  			"Service":   "Tiller",
   356  		},
   357  		"Chart":        chrt.Metadata,
   358  		"Files":        NewFiles(chrt.Files),
   359  		"Capabilities": caps,
   360  	}
   361  
   362  	vals, err := CoalesceValues(chrt, chrtVals)
   363  	if err != nil {
   364  		return top, err
   365  	}
   366  
   367  	top["Values"] = vals
   368  	return top, nil
   369  }
   370  
   371  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   372  func istable(v interface{}) bool {
   373  	_, ok := v.(map[string]interface{})
   374  	return ok
   375  }
   376  
   377  // PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
   378  // The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
   379  // Given the following YAML data the value at path "chapter.one.title" is "Loomings".
   380  //
   381  //	chapter:
   382  //	  one:
   383  //	    title: "Loomings"
   384  func (v Values) PathValue(ypath string) (interface{}, error) {
   385  	if len(ypath) == 0 {
   386  		return nil, errors.New("YAML path string cannot be zero length")
   387  	}
   388  	yps := strings.Split(ypath, ".")
   389  	if len(yps) == 1 {
   390  		// if exists must be root key not table
   391  		vals := v.AsMap()
   392  		k := yps[0]
   393  		if _, ok := vals[k]; ok && !istable(vals[k]) {
   394  			// key found
   395  			return vals[yps[0]], nil
   396  		}
   397  		// key not found
   398  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", k))
   399  	}
   400  	// join all elements of YAML path except last to get string table path
   401  	ypsLen := len(yps)
   402  	table := yps[:ypsLen-1]
   403  	st := strings.Join(table, ".")
   404  	// get the last element as a string key
   405  	key := yps[ypsLen-1:]
   406  	sk := string(key[0])
   407  	// get our table for table path
   408  	t, err := v.Table(st)
   409  	if err != nil {
   410  		//no table
   411  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", sk))
   412  	}
   413  	// check table for key and ensure value is not a table
   414  	if k, ok := t[sk]; ok && !istable(k) {
   415  		// key found
   416  		return k, nil
   417  	}
   418  
   419  	// key not found
   420  	return nil, ErrNoValue(fmt.Errorf("key not found: %s", sk))
   421  }