github.com/migueleliasweb/helm@v2.6.1+incompatible/pkg/chartutil/values.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 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  func tableLookup(v Values, simple string) (Values, error) {
    98  	v2, ok := v[simple]
    99  	if !ok {
   100  		return v, ErrNoTable(fmt.Errorf("no table named %q (%v)", simple, v))
   101  	}
   102  	if vv, ok := v2.(map[string]interface{}); ok {
   103  		return vv, nil
   104  	}
   105  
   106  	// This catches a case where a value is of type Values, but doesn't (for some
   107  	// reason) match the map[string]interface{}. This has been observed in the
   108  	// wild, and might be a result of a nil map of type Values.
   109  	if vv, ok := v2.(Values); ok {
   110  		return vv, nil
   111  	}
   112  
   113  	var e ErrNoTable = fmt.Errorf("no table named %q", simple)
   114  	return map[string]interface{}{}, e
   115  }
   116  
   117  // ReadValues will parse YAML byte data into a Values.
   118  func ReadValues(data []byte) (vals Values, err error) {
   119  	err = yaml.Unmarshal(data, &vals)
   120  	if len(vals) == 0 {
   121  		vals = Values{}
   122  	}
   123  	return
   124  }
   125  
   126  // ReadValuesFile will parse a YAML file into a map of values.
   127  func ReadValuesFile(filename string) (Values, error) {
   128  	data, err := ioutil.ReadFile(filename)
   129  	if err != nil {
   130  		return map[string]interface{}{}, err
   131  	}
   132  	return ReadValues(data)
   133  }
   134  
   135  // CoalesceValues coalesces all of the values in a chart (and its subcharts).
   136  //
   137  // Values are coalesced together using the following rules:
   138  //
   139  //	- Values in a higher level chart always override values in a lower-level
   140  //		dependency chart
   141  //	- Scalar values and arrays are replaced, maps are merged
   142  //	- A chart has access to all of the variables for it, as well as all of
   143  //		the values destined for its dependencies.
   144  func CoalesceValues(chrt *chart.Chart, vals *chart.Config) (Values, error) {
   145  	cvals := Values{}
   146  	// Parse values if not nil. We merge these at the top level because
   147  	// the passed-in values are in the same namespace as the parent chart.
   148  	if vals != nil {
   149  		evals, err := ReadValues([]byte(vals.Raw))
   150  		if err != nil {
   151  			return cvals, err
   152  		}
   153  		cvals, err = coalesce(chrt, evals)
   154  		if err != nil {
   155  			return cvals, err
   156  		}
   157  	}
   158  
   159  	var err error
   160  	cvals, err = coalesceDeps(chrt, cvals)
   161  	return cvals, err
   162  }
   163  
   164  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
   165  //
   166  // This is a helper function for CoalesceValues.
   167  func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   168  	var err error
   169  	dest, err = coalesceValues(ch, dest)
   170  	if err != nil {
   171  		return dest, err
   172  	}
   173  	coalesceDeps(ch, dest)
   174  	return dest, nil
   175  }
   176  
   177  // coalesceDeps coalesces the dependencies of the given chart.
   178  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   179  	for _, subchart := range chrt.Dependencies {
   180  		if c, ok := dest[subchart.Metadata.Name]; !ok {
   181  			// If dest doesn't already have the key, create it.
   182  			dest[subchart.Metadata.Name] = map[string]interface{}{}
   183  		} else if !istable(c) {
   184  			return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c)
   185  		}
   186  		if dv, ok := dest[subchart.Metadata.Name]; ok {
   187  			dvmap := dv.(map[string]interface{})
   188  
   189  			// Get globals out of dest and merge them into dvmap.
   190  			coalesceGlobals(dvmap, dest)
   191  
   192  			var err error
   193  			// Now coalesce the rest of the values.
   194  			dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap)
   195  			if err != nil {
   196  				return dest, err
   197  			}
   198  		}
   199  	}
   200  	return dest, nil
   201  }
   202  
   203  // coalesceGlobals copies the globals out of src and merges them into dest.
   204  //
   205  // For convenience, returns dest.
   206  func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
   207  	var dg, sg map[string]interface{}
   208  
   209  	if destglob, ok := dest[GlobalKey]; !ok {
   210  		dg = map[string]interface{}{}
   211  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
   212  		log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
   213  		return dg
   214  	}
   215  
   216  	if srcglob, ok := src[GlobalKey]; !ok {
   217  		sg = map[string]interface{}{}
   218  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
   219  		log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
   220  		return dg
   221  	}
   222  
   223  	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
   224  	// reverses that decision. It may somehow be possible to introduce a loop
   225  	// here, but I haven't found a way. So for the time being, let's allow
   226  	// tables in globals.
   227  	for key, val := range sg {
   228  		if istable(val) {
   229  			vv := copyMap(val.(map[string]interface{}))
   230  			if destv, ok := dg[key]; ok {
   231  				if destvmap, ok := destv.(map[string]interface{}); ok {
   232  					// Basically, we reverse order of coalesce here to merge
   233  					// top-down.
   234  					coalesceTables(vv, destvmap)
   235  					dg[key] = vv
   236  					continue
   237  				} else {
   238  					log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
   239  				}
   240  			} else {
   241  				// Here there is no merge. We're just adding.
   242  				dg[key] = vv
   243  			}
   244  		} else if dv, ok := dg[key]; ok && istable(dv) {
   245  			// It's not clear if this condition can actually ever trigger.
   246  			log.Printf("key %s is table. Skipping", key)
   247  			continue
   248  		}
   249  		// TODO: Do we need to do any additional checking on the value?
   250  		dg[key] = val
   251  	}
   252  	dest[GlobalKey] = dg
   253  	return dest
   254  }
   255  
   256  func copyMap(src map[string]interface{}) map[string]interface{} {
   257  	dest := make(map[string]interface{}, len(src))
   258  	for k, v := range src {
   259  		dest[k] = v
   260  	}
   261  	return dest
   262  }
   263  
   264  // coalesceValues builds up a values map for a particular chart.
   265  //
   266  // Values in v will override the values in the chart.
   267  func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) {
   268  	// If there are no values in the chart, we just return the given values
   269  	if c.Values == nil || c.Values.Raw == "" {
   270  		return v, nil
   271  	}
   272  
   273  	nv, err := ReadValues([]byte(c.Values.Raw))
   274  	if err != nil {
   275  		// On error, we return just the overridden values.
   276  		// FIXME: We should log this error. It indicates that the YAML data
   277  		// did not parse.
   278  		return v, fmt.Errorf("error reading default values (%s): %s", c.Values.Raw, err)
   279  	}
   280  
   281  	for key, val := range nv {
   282  		if value, ok := v[key]; ok {
   283  			if value == nil {
   284  				// When the YAML value is null, we remove 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  				delete(v, key)
   288  			} else if dest, ok := value.(map[string]interface{}); ok {
   289  				// if v[key] is a table, merge nv's val table into v[key].
   290  				src, ok := val.(map[string]interface{})
   291  				if !ok {
   292  					log.Printf("warning: skipped value for %s: Not a table.", key)
   293  					continue
   294  				}
   295  				// Because v has higher precedence than nv, dest values override src
   296  				// values.
   297  				coalesceTables(dest, src)
   298  			}
   299  		} else {
   300  			// If the key is not in v, copy it from nv.
   301  			v[key] = val
   302  		}
   303  	}
   304  	return v, nil
   305  }
   306  
   307  // coalesceTables merges a source map into a destination map.
   308  //
   309  // dest is considered authoritative.
   310  func coalesceTables(dst, src map[string]interface{}) map[string]interface{} {
   311  	// Because dest has higher precedence than src, dest values override src
   312  	// values.
   313  	for key, val := range src {
   314  		if istable(val) {
   315  			if innerdst, ok := dst[key]; !ok {
   316  				dst[key] = val
   317  			} else if istable(innerdst) {
   318  				coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}))
   319  			} else {
   320  				log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
   321  			}
   322  			continue
   323  		} else if dv, ok := dst[key]; ok && istable(dv) {
   324  			log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
   325  			continue
   326  		} else if !ok { // <- ok is still in scope from preceding conditional.
   327  			dst[key] = val
   328  			continue
   329  		}
   330  	}
   331  	return dst
   332  }
   333  
   334  // ReleaseOptions represents the additional release options needed
   335  // for the composition of the final values struct
   336  type ReleaseOptions struct {
   337  	Name      string
   338  	Time      *timestamp.Timestamp
   339  	Namespace string
   340  	IsUpgrade bool
   341  	IsInstall bool
   342  	Revision  int
   343  }
   344  
   345  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   346  //
   347  // WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will
   348  // remain in the codebase to stay SemVer compliant.
   349  //
   350  // In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter.
   351  func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
   352  	caps := &Capabilities{APIVersions: DefaultVersionSet}
   353  	return ToRenderValuesCaps(chrt, chrtVals, options, caps)
   354  }
   355  
   356  // ToRenderValuesCaps composes the struct from the data coming from the Releases, Charts and Values files
   357  //
   358  // This takes both ReleaseOptions and Capabilities to merge into the render values.
   359  func ToRenderValuesCaps(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions, caps *Capabilities) (Values, error) {
   360  
   361  	top := map[string]interface{}{
   362  		"Release": map[string]interface{}{
   363  			"Name":      options.Name,
   364  			"Time":      options.Time,
   365  			"Namespace": options.Namespace,
   366  			"IsUpgrade": options.IsUpgrade,
   367  			"IsInstall": options.IsInstall,
   368  			"Revision":  options.Revision,
   369  			"Service":   "Tiller",
   370  		},
   371  		"Chart":        chrt.Metadata,
   372  		"Files":        NewFiles(chrt.Files),
   373  		"Capabilities": caps,
   374  	}
   375  
   376  	vals, err := CoalesceValues(chrt, chrtVals)
   377  	if err != nil {
   378  		return top, err
   379  	}
   380  
   381  	top["Values"] = vals
   382  	return top, nil
   383  }
   384  
   385  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   386  func istable(v interface{}) bool {
   387  	_, ok := v.(map[string]interface{})
   388  	return ok
   389  }
   390  
   391  // PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
   392  // The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
   393  // Given the following YAML data the value at path "chapter.one.title" is "Loomings".
   394  //
   395  //	chapter:
   396  //	  one:
   397  //	    title: "Loomings"
   398  func (v Values) PathValue(ypath string) (interface{}, error) {
   399  	if len(ypath) == 0 {
   400  		return nil, errors.New("YAML path string cannot be zero length")
   401  	}
   402  	yps := strings.Split(ypath, ".")
   403  	if len(yps) == 1 {
   404  		// if exists must be root key not table
   405  		vals := v.AsMap()
   406  		k := yps[0]
   407  		if _, ok := vals[k]; ok && !istable(vals[k]) {
   408  			// key found
   409  			return vals[yps[0]], nil
   410  		}
   411  		// key not found
   412  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", k))
   413  	}
   414  	// join all elements of YAML path except last to get string table path
   415  	ypsLen := len(yps)
   416  	table := yps[:ypsLen-1]
   417  	st := strings.Join(table, ".")
   418  	// get the last element as a string key
   419  	key := yps[ypsLen-1:]
   420  	sk := string(key[0])
   421  	// get our table for table path
   422  	t, err := v.Table(st)
   423  	if err != nil {
   424  		//no table
   425  		return nil, ErrNoValue(fmt.Errorf("%v is not a value", sk))
   426  	}
   427  	// check table for key and ensure value is not a table
   428  	if k, ok := t[sk]; ok && !istable(k) {
   429  		// key found
   430  		return k, nil
   431  	}
   432  
   433  	// key not found
   434  	return nil, ErrNoValue(fmt.Errorf("key not found: %s", sk))
   435  }