github.com/felipejfc/helm@v2.1.2+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  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"log"
    24  	"strings"
    25  
    26  	"github.com/ghodss/yaml"
    27  	"github.com/golang/protobuf/ptypes/timestamp"
    28  	"k8s.io/helm/pkg/proto/hapi/chart"
    29  )
    30  
    31  // ErrNoTable indicates that a chart does not have a matching table.
    32  type ErrNoTable error
    33  
    34  // GlobalKey is the name of the Values key that is used for storing global vars.
    35  const GlobalKey = "global"
    36  
    37  // Values represents a collection of chart values.
    38  type Values map[string]interface{}
    39  
    40  // YAML encodes the Values into a YAML string.
    41  func (v Values) YAML() (string, error) {
    42  	b, err := yaml.Marshal(v)
    43  	return string(b), err
    44  }
    45  
    46  // Table gets a table (YAML subsection) from a Values object.
    47  //
    48  // The table is returned as a Values.
    49  //
    50  // Compound table names may be specified with dots:
    51  //
    52  //	foo.bar
    53  //
    54  // The above will be evaluated as "The table bar inside the table
    55  // foo".
    56  //
    57  // An ErrNoTable is returned if the table does not exist.
    58  func (v Values) Table(name string) (Values, error) {
    59  	names := strings.Split(name, ".")
    60  	table := v
    61  	var err error
    62  
    63  	for _, n := range names {
    64  		table, err = tableLookup(table, n)
    65  		if err != nil {
    66  			return table, err
    67  		}
    68  	}
    69  	return table, err
    70  }
    71  
    72  // AsMap is a utility function for converting Values to a map[string]interface{}.
    73  //
    74  // It protects against nil map panics.
    75  func (v Values) AsMap() map[string]interface{} {
    76  	if v == nil || len(v) == 0 {
    77  		return map[string]interface{}{}
    78  	}
    79  	return v
    80  }
    81  
    82  // Encode writes serialized Values information to the given io.Writer.
    83  func (v Values) Encode(w io.Writer) error {
    84  	//return yaml.NewEncoder(w).Encode(v)
    85  	out, err := yaml.Marshal(v)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	_, err = w.Write(out)
    90  	return err
    91  }
    92  
    93  func tableLookup(v Values, simple string) (Values, error) {
    94  	v2, ok := v[simple]
    95  	if !ok {
    96  		return v, ErrNoTable(fmt.Errorf("no table named %q (%v)", simple, v))
    97  	}
    98  	if vv, ok := v2.(map[string]interface{}); ok {
    99  		return vv, nil
   100  	}
   101  
   102  	// This catches a case where a value is of type Values, but doesn't (for some
   103  	// reason) match the map[string]interface{}. This has been observed in the
   104  	// wild, and might be a result of a nil map of type Values.
   105  	if vv, ok := v2.(Values); ok {
   106  		return vv, nil
   107  	}
   108  
   109  	var e ErrNoTable = fmt.Errorf("no table named %q", simple)
   110  	return map[string]interface{}{}, e
   111  }
   112  
   113  // ReadValues will parse YAML byte data into a Values.
   114  func ReadValues(data []byte) (vals Values, err error) {
   115  	err = yaml.Unmarshal(data, &vals)
   116  	if len(vals) == 0 {
   117  		vals = Values{}
   118  	}
   119  	return
   120  }
   121  
   122  // ReadValuesFile will parse a YAML file into a map of values.
   123  func ReadValuesFile(filename string) (Values, error) {
   124  	data, err := ioutil.ReadFile(filename)
   125  	if err != nil {
   126  		return map[string]interface{}{}, err
   127  	}
   128  	return ReadValues(data)
   129  }
   130  
   131  // CoalesceValues coalesces all of the values in a chart (and its subcharts).
   132  //
   133  // Values are coalesced together using the following rules:
   134  //
   135  //	- Values in a higher level chart always override values in a lower-level
   136  //		dependency chart
   137  //	- Scalar values and arrays are replaced, maps are merged
   138  //	- A chart has access to all of the variables for it, as well as all of
   139  //		the values destined for its dependencies.
   140  func CoalesceValues(chrt *chart.Chart, vals *chart.Config) (Values, error) {
   141  	cvals := Values{}
   142  	// Parse values if not nil. We merge these at the top level because
   143  	// the passed-in values are in the same namespace as the parent chart.
   144  	if vals != nil {
   145  		evals, err := ReadValues([]byte(vals.Raw))
   146  		if err != nil {
   147  			return cvals, err
   148  		}
   149  		cvals, err = coalesce(chrt, evals)
   150  		if err != nil {
   151  			return cvals, err
   152  		}
   153  	}
   154  
   155  	var err error
   156  	cvals, err = coalesceDeps(chrt, cvals)
   157  	return cvals, err
   158  }
   159  
   160  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
   161  //
   162  // This is a helper function for CoalesceValues.
   163  func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   164  	var err error
   165  	dest, err = coalesceValues(ch, dest)
   166  	if err != nil {
   167  		return dest, err
   168  	}
   169  	coalesceDeps(ch, dest)
   170  	return dest, nil
   171  }
   172  
   173  // coalesceDeps coalesces the dependencies of the given chart.
   174  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
   175  	for _, subchart := range chrt.Dependencies {
   176  		if c, ok := dest[subchart.Metadata.Name]; !ok {
   177  			// If dest doesn't already have the key, create it.
   178  			dest[subchart.Metadata.Name] = map[string]interface{}{}
   179  		} else if !istable(c) {
   180  			return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c)
   181  		}
   182  		if dv, ok := dest[subchart.Metadata.Name]; ok {
   183  			dvmap := dv.(map[string]interface{})
   184  
   185  			// Get globals out of dest and merge them into dvmap.
   186  			coalesceGlobals(dvmap, dest)
   187  
   188  			var err error
   189  			// Now coalesce the rest of the values.
   190  			dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap)
   191  			if err != nil {
   192  				return dest, err
   193  			}
   194  		}
   195  	}
   196  	return dest, nil
   197  }
   198  
   199  // coalesceGlobals copies the globals out of src and merges them into dest.
   200  //
   201  // For convenience, returns dest.
   202  func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
   203  	var dg, sg map[string]interface{}
   204  
   205  	if destglob, ok := dest[GlobalKey]; !ok {
   206  		dg = map[string]interface{}{}
   207  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
   208  		log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
   209  		return dg
   210  	}
   211  
   212  	if srcglob, ok := src[GlobalKey]; !ok {
   213  		sg = map[string]interface{}{}
   214  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
   215  		log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
   216  		return dg
   217  	}
   218  
   219  	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
   220  	// reverses that decision. It may somehow be possible to introduce a loop
   221  	// here, but I haven't found a way. So for the time being, let's allow
   222  	// tables in globals.
   223  	for key, val := range sg {
   224  		if istable(val) {
   225  			vv := copyMap(val.(map[string]interface{}))
   226  			if destv, ok := dg[key]; ok {
   227  				if destvmap, ok := destv.(map[string]interface{}); ok {
   228  					// Basically, we reverse order of coalesce here to merge
   229  					// top-down.
   230  					coalesceTables(vv, destvmap)
   231  					dg[key] = vv
   232  					continue
   233  				} else {
   234  					log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
   235  				}
   236  			} else {
   237  				// Here there is no merge. We're just adding.
   238  				dg[key] = vv
   239  			}
   240  		} else if dv, ok := dg[key]; ok && istable(dv) {
   241  			// It's not clear if this condition can actually ever trigger.
   242  			log.Printf("key %s is table. Skipping", key)
   243  			continue
   244  		}
   245  		// TODO: Do we need to do any additional checking on the value?
   246  		dg[key] = val
   247  	}
   248  	dest[GlobalKey] = dg
   249  	return dest
   250  }
   251  
   252  func copyMap(src map[string]interface{}) map[string]interface{} {
   253  	dest := make(map[string]interface{}, len(src))
   254  	for k, v := range src {
   255  		dest[k] = v
   256  	}
   257  	return dest
   258  }
   259  
   260  // coalesceValues builds up a values map for a particular chart.
   261  //
   262  // Values in v will override the values in the chart.
   263  func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) {
   264  	// If there are no values in the chart, we just return the given values
   265  	if c.Values == nil || c.Values.Raw == "" {
   266  		return v, nil
   267  	}
   268  
   269  	nv, err := ReadValues([]byte(c.Values.Raw))
   270  	if err != nil {
   271  		// On error, we return just the overridden values.
   272  		// FIXME: We should log this error. It indicates that the YAML data
   273  		// did not parse.
   274  		return v, fmt.Errorf("error reading default values (%s): %s", c.Values.Raw, err)
   275  	}
   276  
   277  	for key, val := range nv {
   278  		if _, ok := v[key]; !ok {
   279  			// If the key is not in v, copy it from nv.
   280  			v[key] = val
   281  		} else if dest, ok := v[key].(map[string]interface{}); ok {
   282  			// if v[key] is a table, merge nv's val table into v[key].
   283  			src, ok := val.(map[string]interface{})
   284  			if !ok {
   285  				log.Printf("warning: skipped value for %s: Not a table.", key)
   286  				continue
   287  			}
   288  			// Because v has higher precedence than nv, dest values override src
   289  			// values.
   290  			coalesceTables(dest, src)
   291  		}
   292  	}
   293  	return v, nil
   294  }
   295  
   296  // coalesceTables merges a source map into a destination map.
   297  //
   298  // dest is considered authoritative.
   299  func coalesceTables(dst, src map[string]interface{}) map[string]interface{} {
   300  	// Because dest has higher precedence than src, dest values override src
   301  	// values.
   302  	for key, val := range src {
   303  		if istable(val) {
   304  			if innerdst, ok := dst[key]; !ok {
   305  				dst[key] = val
   306  			} else if istable(innerdst) {
   307  				coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}))
   308  			} else {
   309  				log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
   310  			}
   311  			continue
   312  		} else if dv, ok := dst[key]; ok && istable(dv) {
   313  			log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
   314  			continue
   315  		} else if !ok { // <- ok is still in scope from preceding conditional.
   316  			dst[key] = val
   317  			continue
   318  		}
   319  	}
   320  	return dst
   321  }
   322  
   323  // ReleaseOptions represents the additional release options needed
   324  // for the composition of the final values struct
   325  type ReleaseOptions struct {
   326  	Name      string
   327  	Time      *timestamp.Timestamp
   328  	Namespace string
   329  }
   330  
   331  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   332  func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
   333  
   334  	top := map[string]interface{}{
   335  		"Release": map[string]interface{}{
   336  			"Name":      options.Name,
   337  			"Time":      options.Time,
   338  			"Namespace": options.Namespace,
   339  			"Service":   "Tiller",
   340  		},
   341  		"Chart": chrt.Metadata,
   342  		"Files": NewFiles(chrt.Files),
   343  	}
   344  
   345  	vals, err := CoalesceValues(chrt, chrtVals)
   346  	if err != nil {
   347  		return top, err
   348  	}
   349  
   350  	top["Values"] = vals
   351  	return top, nil
   352  }
   353  
   354  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   355  func istable(v interface{}) bool {
   356  	_, ok := v.(map[string]interface{})
   357  	return ok
   358  }