github.com/Beeketing/helm@v2.12.1+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  		cvals, err = coalesce(chrt, evals)
   170  		if err != nil {
   171  			return cvals, err
   172  		}
   173  	}
   174  
   175  	var err error
   176  	cvals, err = coalesceDeps(chrt, cvals)
   177  	return cvals, err
   178  }
   179  
   180  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
   181  //
   182  // This is a helper function for CoalesceValues.
   183  func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   184  	var err error
   185  	dest, err = coalesceValues(ch, dest)
   186  	if err != nil {
   187  		return dest, err
   188  	}
   189  	coalesceDeps(ch, dest)
   190  	return dest, nil
   191  }
   192  
   193  // coalesceDeps coalesces the dependencies of the given chart.
   194  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   195  	for _, subchart := range chrt.Dependencies {
   196  		if c, ok := dest[subchart.Metadata.Name]; !ok {
   197  			// If dest doesn't already have the key, create it.
   198  			dest[subchart.Metadata.Name] = map[string]interface{}{}
   199  		} else if !istable(c) {
   200  			return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c)
   201  		}
   202  		if dv, ok := dest[subchart.Metadata.Name]; ok {
   203  			dvmap := dv.(map[string]interface{})
   204  
   205  			// Get globals out of dest and merge them into dvmap.
   206  			coalesceGlobals(dvmap, dest, chrt.Metadata.Name)
   207  
   208  			var err error
   209  			// Now coalesce the rest of the values.
   210  			dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap)
   211  			if err != nil {
   212  				return dest, err
   213  			}
   214  		}
   215  	}
   216  	return dest, nil
   217  }
   218  
   219  // coalesceGlobals copies the globals out of src and merges them into dest.
   220  //
   221  // For convenience, returns dest.
   222  func coalesceGlobals(dest, src map[string]interface{}, chartName string) map[string]interface{} {
   223  	var dg, sg map[string]interface{}
   224  
   225  	if destglob, ok := dest[GlobalKey]; !ok {
   226  		dg = map[string]interface{}{}
   227  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
   228  		log.Printf("Warning: Skipping globals for chart '%s' because destination '%s' is not a table.", chartName, GlobalKey)
   229  		return dg
   230  	}
   231  
   232  	if srcglob, ok := src[GlobalKey]; !ok {
   233  		sg = map[string]interface{}{}
   234  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
   235  		log.Printf("Warning: skipping globals for chart '%s' because source '%s' is not a table.", chartName, GlobalKey)
   236  		return dg
   237  	}
   238  
   239  	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
   240  	// reverses that decision. It may somehow be possible to introduce a loop
   241  	// here, but I haven't found a way. So for the time being, let's allow
   242  	// tables in globals.
   243  	for key, val := range sg {
   244  		if istable(val) {
   245  			vv := copyMap(val.(map[string]interface{}))
   246  			if destv, ok := dg[key]; ok {
   247  				if destvmap, ok := destv.(map[string]interface{}); ok {
   248  					// Basically, we reverse order of coalesce here to merge
   249  					// top-down.
   250  					coalesceTables(vv, destvmap, chartName)
   251  					dg[key] = vv
   252  					continue
   253  				} else {
   254  					log.Printf("Warning: For chart '%s', cannot merge map onto non-map for key '%q'. Skipping.", chartName, key)
   255  				}
   256  			} else {
   257  				// Here there is no merge. We're just adding.
   258  				dg[key] = vv
   259  			}
   260  		} else if dv, ok := dg[key]; ok && istable(dv) {
   261  			// It's not clear if this condition can actually ever trigger.
   262  			log.Printf("Warning: For chart '%s', key '%s' is a table. Skipping.", chartName, key)
   263  			continue
   264  		}
   265  		// TODO: Do we need to do any additional checking on the value?
   266  		dg[key] = val
   267  	}
   268  	dest[GlobalKey] = dg
   269  	return dest
   270  }
   271  
   272  func copyMap(src map[string]interface{}) map[string]interface{} {
   273  	dest := make(map[string]interface{}, len(src))
   274  	for k, v := range src {
   275  		dest[k] = v
   276  	}
   277  	return dest
   278  }
   279  
   280  // coalesceValues builds up a values map for a particular chart.
   281  //
   282  // Values in v will override the values in the chart.
   283  func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) {
   284  	// If there are no values in the chart, we just return the given values
   285  	if c.Values == nil || c.Values.Raw == "" {
   286  		return v, nil
   287  	}
   288  
   289  	nv, err := ReadValues([]byte(c.Values.Raw))
   290  	if err != nil {
   291  		// On error, we return just the overridden values.
   292  		// FIXME: We should log this error. It indicates that the YAML data
   293  		// did not parse.
   294  		return v, fmt.Errorf("Error: Reading chart '%s' default values (%s): %s", c.Metadata.Name, c.Values.Raw, err)
   295  	}
   296  
   297  	for key, val := range nv {
   298  		if value, ok := v[key]; ok {
   299  			if value == nil {
   300  				// When the YAML value is null, we remove the value's key.
   301  				// This allows Helm's various sources of values (value files or --set) to
   302  				// remove incompatible keys from any previous chart, file, or set values.
   303  				delete(v, key)
   304  			} else if dest, ok := value.(map[string]interface{}); ok {
   305  				// if v[key] is a table, merge nv's val table into v[key].
   306  				src, ok := val.(map[string]interface{})
   307  				if !ok {
   308  					log.Printf("Warning: Building values map for chart '%s'. Skipped value (%+v) for '%s', as it is not a table.", c.Metadata.Name, src, key)
   309  					continue
   310  				}
   311  				// Because v has higher precedence than nv, dest values override src
   312  				// values.
   313  				coalesceTables(dest, src, c.Metadata.Name)
   314  			}
   315  		} else {
   316  			// If the key is not in v, copy it from nv.
   317  			v[key] = val
   318  		}
   319  	}
   320  	return v, nil
   321  }
   322  
   323  // coalesceTables merges a source map into a destination map.
   324  //
   325  // dest is considered authoritative.
   326  func coalesceTables(dst, src map[string]interface{}, chartName string) map[string]interface{} {
   327  	// Because dest has higher precedence than src, dest values override src
   328  	// values.
   329  	for key, val := range src {
   330  		if istable(val) {
   331  			if innerdst, ok := dst[key]; !ok {
   332  				dst[key] = val
   333  			} else if istable(innerdst) {
   334  				coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}), chartName)
   335  			} else {
   336  				log.Printf("Warning: Merging destination map for chart '%s'. Cannot overwrite table item '%s', with non table value: %v", chartName, key, val)
   337  			}
   338  			continue
   339  		} else if dv, ok := dst[key]; ok && istable(dv) {
   340  			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)
   341  			continue
   342  		} else if !ok { // <- ok is still in scope from preceding conditional.
   343  			dst[key] = val
   344  			continue
   345  		}
   346  	}
   347  	return dst
   348  }
   349  
   350  // ReleaseOptions represents the additional release options needed
   351  // for the composition of the final values struct
   352  type ReleaseOptions struct {
   353  	Name      string
   354  	Time      *timestamp.Timestamp
   355  	Namespace string
   356  	IsUpgrade bool
   357  	IsInstall bool
   358  	Revision  int
   359  }
   360  
   361  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   362  //
   363  // WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will
   364  // remain in the codebase to stay SemVer compliant.
   365  //
   366  // In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter.
   367  func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
   368  	caps := &Capabilities{APIVersions: DefaultVersionSet}
   369  	return ToRenderValuesCaps(chrt, chrtVals, options, caps)
   370  }
   371  
   372  // ToRenderValuesCaps composes the struct from the data coming from the Releases, Charts and Values files
   373  //
   374  // This takes both ReleaseOptions and Capabilities to merge into the render values.
   375  func ToRenderValuesCaps(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions, caps *Capabilities) (Values, error) {
   376  
   377  	top := map[string]interface{}{
   378  		"Release": map[string]interface{}{
   379  			"Name":      options.Name,
   380  			"Time":      options.Time,
   381  			"Namespace": options.Namespace,
   382  			"IsUpgrade": options.IsUpgrade,
   383  			"IsInstall": options.IsInstall,
   384  			"Revision":  options.Revision,
   385  			"Service":   "Tiller",
   386  		},
   387  		"Chart":        chrt.Metadata,
   388  		"Files":        NewFiles(chrt.Files),
   389  		"Capabilities": caps,
   390  	}
   391  
   392  	vals, err := CoalesceValues(chrt, chrtVals)
   393  	if err != nil {
   394  		return top, err
   395  	}
   396  
   397  	top["Values"] = vals
   398  	return top, nil
   399  }
   400  
   401  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   402  func istable(v interface{}) bool {
   403  	_, ok := v.(map[string]interface{})
   404  	return ok
   405  }
   406  
   407  // PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
   408  // The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
   409  // Given the following YAML data the value at path "chapter.one.title" is "Loomings".
   410  //
   411  //	chapter:
   412  //	  one:
   413  //	    title: "Loomings"
   414  func (v Values) PathValue(ypath string) (interface{}, error) {
   415  	if len(ypath) == 0 {
   416  		return nil, errors.New("YAML path string cannot be zero length")
   417  	}
   418  	yps := strings.Split(ypath, ".")
   419  	if len(yps) == 1 {
   420  		// if exists must be root key not table
   421  		vals := v.AsMap()
   422  		k := yps[0]
   423  		if _, ok := vals[k]; ok && !istable(vals[k]) {
   424  			// key found
   425  			return vals[yps[0]], nil
   426  		}
   427  		// key not found
   428  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", k))
   429  	}
   430  	// join all elements of YAML path except last to get string table path
   431  	ypsLen := len(yps)
   432  	table := yps[:ypsLen-1]
   433  	st := strings.Join(table, ".")
   434  	// get the last element as a string key
   435  	key := yps[ypsLen-1:]
   436  	sk := string(key[0])
   437  	// get our table for table path
   438  	t, err := v.Table(st)
   439  	if err != nil {
   440  		//no table
   441  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", sk))
   442  	}
   443  	// check table for key and ensure value is not a table
   444  	if k, ok := t[sk]; ok && !istable(k) {
   445  		// key found
   446  		return k, nil
   447  	}
   448  
   449  	// key not found
   450  	return nil, ErrNoValue(fmt.Errorf("key not found: %s", sk))
   451  }