github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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 = coalesce(chrt, evals)
   150  	}
   151  
   152  	cvals = coalesceDeps(chrt, cvals)
   153  
   154  	return cvals, nil
   155  }
   156  
   157  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
   158  //
   159  // This is a helper function for CoalesceValues.
   160  func coalesce(ch *chart.Chart, dest map[string]interface{}) map[string]interface{} {
   161  	dest = coalesceValues(ch, dest)
   162  	coalesceDeps(ch, dest)
   163  	return dest
   164  }
   165  
   166  // coalesceDeps coalesces the dependencies of the given chart.
   167  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) map[string]interface{} {
   168  	for _, subchart := range chrt.Dependencies {
   169  		if c, ok := dest[subchart.Metadata.Name]; !ok {
   170  			// If dest doesn't already have the key, create it.
   171  			dest[subchart.Metadata.Name] = map[string]interface{}{}
   172  		} else if !istable(c) {
   173  			log.Printf("error: type mismatch on %s: %t", subchart.Metadata.Name, c)
   174  			return dest
   175  		}
   176  		if dv, ok := dest[subchart.Metadata.Name]; ok {
   177  			dvmap := dv.(map[string]interface{})
   178  
   179  			// Get globals out of dest and merge them into dvmap.
   180  			coalesceGlobals(dvmap, dest)
   181  
   182  			// Now coalesce the rest of the values.
   183  			dest[subchart.Metadata.Name] = coalesce(subchart, dvmap)
   184  		}
   185  	}
   186  	return dest
   187  }
   188  
   189  // coalesceGlobals copies the globals out of src and merges them into dest.
   190  //
   191  // For convenience, returns dest.
   192  func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
   193  	var dg, sg map[string]interface{}
   194  
   195  	if destglob, ok := dest[GlobalKey]; !ok {
   196  		dg = map[string]interface{}{}
   197  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
   198  		log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
   199  		return dg
   200  	}
   201  
   202  	if srcglob, ok := src[GlobalKey]; !ok {
   203  		sg = map[string]interface{}{}
   204  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
   205  		log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
   206  		return dg
   207  	}
   208  
   209  	// We manually copy (instead of using coalesceTables) because (a) we need
   210  	// to prevent loops, and (b) we disallow nesting tables under globals.
   211  	// Globals should _just_ be k/v pairs.
   212  	for key, val := range sg {
   213  		if istable(val) {
   214  			log.Printf("warning: nested values are illegal in globals (%s)", key)
   215  			continue
   216  		} else if dv, ok := dg[key]; ok && istable(dv) {
   217  			log.Printf("warning: nested values are illegal in globals (%s)", key)
   218  			continue
   219  		}
   220  		// TODO: Do we need to do any additional checking on the value?
   221  		dg[key] = val
   222  	}
   223  	dest[GlobalKey] = dg
   224  	return dest
   225  
   226  }
   227  
   228  // coalesceValues builds up a values map for a particular chart.
   229  //
   230  // Values in v will override the values in the chart.
   231  func coalesceValues(c *chart.Chart, v map[string]interface{}) map[string]interface{} {
   232  	// If there are no values in the chart, we just return the given values
   233  	if c.Values == nil || c.Values.Raw == "" {
   234  		return v
   235  	}
   236  
   237  	nv, err := ReadValues([]byte(c.Values.Raw))
   238  	if err != nil {
   239  		// On error, we return just the overridden values.
   240  		// FIXME: We should log this error. It indicates that the YAML data
   241  		// did not parse.
   242  		log.Printf("error reading default values (%s): %s", c.Values.Raw, err)
   243  		return v
   244  	}
   245  
   246  	for key, val := range nv {
   247  		if _, ok := v[key]; !ok {
   248  			// If the key is not in v, copy it from nv.
   249  			v[key] = val
   250  		} else if dest, ok := v[key].(map[string]interface{}); ok {
   251  			// if v[key] is a table, merge nv's val table into v[key].
   252  			src, ok := val.(map[string]interface{})
   253  			if !ok {
   254  				log.Printf("warning: skipped value for %s: Not a table.", key)
   255  				continue
   256  			}
   257  			// Because v has higher precedence than nv, dest values override src
   258  			// values.
   259  			coalesceTables(dest, src)
   260  		}
   261  	}
   262  	return v
   263  }
   264  
   265  // coalesceTables merges a source map into a destination map.
   266  //
   267  // dest is considered authoritative.
   268  func coalesceTables(dst, src map[string]interface{}) map[string]interface{} {
   269  	// Because dest has higher precedence than src, dest values override src
   270  	// values.
   271  	for key, val := range src {
   272  		if istable(val) {
   273  			if innerdst, ok := dst[key]; !ok {
   274  				dst[key] = val
   275  			} else if istable(innerdst) {
   276  				coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}))
   277  			} else {
   278  				log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
   279  			}
   280  			continue
   281  		} else if dv, ok := dst[key]; ok && istable(dv) {
   282  			log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
   283  			continue
   284  		} else if !ok { // <- ok is still in scope from preceding conditional.
   285  			dst[key] = val
   286  			continue
   287  		}
   288  	}
   289  	return dst
   290  }
   291  
   292  // ReleaseOptions represents the additional release options needed
   293  // for the composition of the final values struct
   294  type ReleaseOptions struct {
   295  	Name      string
   296  	Time      *timestamp.Timestamp
   297  	Namespace string
   298  }
   299  
   300  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   301  func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
   302  
   303  	top := map[string]interface{}{
   304  		"Release": map[string]interface{}{
   305  			"Name":      options.Name,
   306  			"Time":      options.Time,
   307  			"Namespace": options.Namespace,
   308  			"Service":   "Tiller",
   309  		},
   310  		"Chart": chrt.Metadata,
   311  		"Files": NewFiles(chrt.Files),
   312  	}
   313  
   314  	vals, err := CoalesceValues(chrt, chrtVals)
   315  	if err != nil {
   316  		return top, err
   317  	}
   318  
   319  	top["Values"] = vals
   320  	return top, nil
   321  }
   322  
   323  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   324  func istable(v interface{}) bool {
   325  	_, ok := v.(map[string]interface{})
   326  	return ok
   327  }