mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
7.7 KiB
273 lines
7.7 KiB
package chartutil
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"k8s.io/helm/pkg/proto/hapi/chart"
|
|
)
|
|
|
|
// ErrNoTable indicates that a chart does not have a matching table.
|
|
var ErrNoTable = errors.New("no table")
|
|
|
|
// GlobalKey is the name of the Values key that is used for storing global vars.
|
|
const GlobalKey = "global"
|
|
|
|
// Values represents a collection of chart values.
|
|
type Values map[string]interface{}
|
|
|
|
// YAML encodes the Values into a YAML string.
|
|
func (v Values) YAML() (string, error) {
|
|
b, err := yaml.Marshal(v)
|
|
return string(b), err
|
|
}
|
|
|
|
// Table gets a table (YAML subsection) from a Values object.
|
|
//
|
|
// The table is returned as a Values.
|
|
//
|
|
// Compound table names may be specified with dots:
|
|
//
|
|
// foo.bar
|
|
//
|
|
// The above will be evaluated as "The table bar inside the table
|
|
// foo".
|
|
//
|
|
// An ErrNoTable is returned if the table does not exist.
|
|
func (v Values) Table(name string) (Values, error) {
|
|
names := strings.Split(name, ".")
|
|
table := v
|
|
var err error
|
|
|
|
for _, n := range names {
|
|
table, err = tableLookup(table, n)
|
|
if err != nil {
|
|
return table, err
|
|
}
|
|
}
|
|
return table, err
|
|
}
|
|
|
|
// AsMap is a utility function for converting Values to a map[string]interface{}.
|
|
//
|
|
// It protects against nil map panics.
|
|
func (v Values) AsMap() map[string]interface{} {
|
|
if v == nil || len(v) == 0 {
|
|
return map[string]interface{}{}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Encode writes serialized Values information to the given io.Writer.
|
|
func (v Values) Encode(w io.Writer) error {
|
|
//return yaml.NewEncoder(w).Encode(v)
|
|
out, err := yaml.Marshal(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(out)
|
|
return err
|
|
}
|
|
|
|
func tableLookup(v Values, simple string) (Values, error) {
|
|
v2, ok := v[simple]
|
|
if !ok {
|
|
return v, ErrNoTable
|
|
}
|
|
vv, ok := v2.(map[string]interface{})
|
|
if !ok {
|
|
return vv, ErrNoTable
|
|
}
|
|
return vv, nil
|
|
}
|
|
|
|
// ReadValues will parse YAML byte data into a Values.
|
|
func ReadValues(data []byte) (vals Values, err error) {
|
|
vals = make(map[string]interface{})
|
|
if len(data) > 0 {
|
|
err = yaml.Unmarshal(data, &vals)
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadValuesFile will parse a YAML file into a map of values.
|
|
func ReadValuesFile(filename string) (Values, error) {
|
|
data, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return map[string]interface{}{}, err
|
|
}
|
|
return ReadValues(data)
|
|
}
|
|
|
|
// CoalesceValues coalesces all of the values in a chart (and its subcharts).
|
|
//
|
|
// The overrides map may be used to specifically override configuration values.
|
|
//
|
|
// Values are coalesced together using the following rules:
|
|
//
|
|
// - Values in a higher level chart always override values in a lower-level
|
|
// dependency chart
|
|
// - Scalar values and arrays are replaced, maps are merged
|
|
// - A chart has access to all of the variables for it, as well as all of
|
|
// the values destined for its dependencies.
|
|
func CoalesceValues(chrt *chart.Chart, vals *chart.Config, overrides map[string]interface{}) (Values, error) {
|
|
var cvals Values
|
|
// Parse values if not nil. We merge these at the top level because
|
|
// the passed-in values are in the same namespace as the parent chart.
|
|
if vals != nil {
|
|
evals, err := ReadValues([]byte(vals.Raw))
|
|
if err != nil {
|
|
return cvals, err
|
|
}
|
|
// Override the top-level values. Overrides are NEVER merged deeply.
|
|
// The assumption is that an override is intended to set an explicit
|
|
// and exact value.
|
|
for k, v := range overrides {
|
|
evals[k] = v
|
|
}
|
|
cvals = coalesceValues(chrt, evals)
|
|
} else if len(overrides) > 0 {
|
|
cvals = coalesceValues(chrt, overrides)
|
|
}
|
|
|
|
cvals = coalesceDeps(chrt, cvals)
|
|
|
|
return cvals, nil
|
|
}
|
|
|
|
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
|
|
//
|
|
// This is a helper function for CoalesceValues.
|
|
func coalesce(ch *chart.Chart, dest map[string]interface{}) map[string]interface{} {
|
|
dest = coalesceValues(ch, dest)
|
|
coalesceDeps(ch, dest)
|
|
return dest
|
|
}
|
|
|
|
// coalesceDeps coalesces the dependencies of the given chart.
|
|
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) map[string]interface{} {
|
|
for _, subchart := range chrt.Dependencies {
|
|
if c, ok := dest[subchart.Metadata.Name]; !ok {
|
|
// If dest doesn't already have the key, create it.
|
|
dest[subchart.Metadata.Name] = map[string]interface{}{}
|
|
} else if !istable(c) {
|
|
log.Printf("error: type mismatch on %s: %t", subchart.Metadata.Name, c)
|
|
return dest
|
|
}
|
|
if dv, ok := dest[subchart.Metadata.Name]; ok {
|
|
dvmap := dv.(map[string]interface{})
|
|
|
|
// Get globals out of dest and merge them into dvmap.
|
|
coalesceGlobals(dvmap, dest)
|
|
|
|
// Now coalesce the rest of the values.
|
|
dest[subchart.Metadata.Name] = coalesce(subchart, dvmap)
|
|
}
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// coalesceGlobals copies the globals out of src and merges them into dest.
|
|
//
|
|
// For convenience, returns dest.
|
|
func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
|
|
var dg, sg map[string]interface{}
|
|
|
|
if destglob, ok := dest[GlobalKey]; !ok {
|
|
dg = map[string]interface{}{}
|
|
} else if dg, ok = destglob.(map[string]interface{}); !ok {
|
|
log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
|
|
return dg
|
|
}
|
|
|
|
if srcglob, ok := src[GlobalKey]; !ok {
|
|
sg = map[string]interface{}{}
|
|
} else if sg, ok = srcglob.(map[string]interface{}); !ok {
|
|
log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
|
|
return dg
|
|
}
|
|
|
|
// We manually copy (instead of using coalesceTables) because (a) we need
|
|
// to prevent loops, and (b) we disallow nesting tables under globals.
|
|
// Globals should _just_ be k/v pairs.
|
|
for key, val := range sg {
|
|
if istable(val) {
|
|
log.Printf("warning: nested values are illegal in globals (%s)", key)
|
|
continue
|
|
} else if dv, ok := dg[key]; ok && istable(dv) {
|
|
log.Printf("warning: nested values are illegal in globals (%s)", key)
|
|
continue
|
|
}
|
|
// TODO: Do we need to do any additional checking on the value?
|
|
dg[key] = val
|
|
}
|
|
dest[GlobalKey] = dg
|
|
return dest
|
|
|
|
}
|
|
|
|
// coalesceValues builds up a values map for a particular chart.
|
|
//
|
|
// Values in v will override the values in the chart.
|
|
func coalesceValues(c *chart.Chart, v map[string]interface{}) map[string]interface{} {
|
|
// If there are no values in the chart, we just return the given values
|
|
if c.Values == nil || c.Values.Raw == "" {
|
|
return v
|
|
}
|
|
|
|
nv, err := ReadValues([]byte(c.Values.Raw))
|
|
if err != nil {
|
|
// On error, we return just the overridden values.
|
|
// FIXME: We should log this error. It indicates that the YAML data
|
|
// did not parse.
|
|
log.Printf("error reading default values (%s): %s", c.Values.Raw, err)
|
|
return v
|
|
}
|
|
|
|
for key, val := range nv {
|
|
if _, ok := v[key]; !ok {
|
|
v[key] = val
|
|
} else if dest, ok := v[key].(map[string]interface{}); ok {
|
|
src, ok := val.(map[string]interface{})
|
|
if !ok {
|
|
log.Printf("warning: skipped value for %s: Not a table.", key)
|
|
continue
|
|
}
|
|
// coalesce tables
|
|
coalesceTables(dest, src)
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// coalesceTables merges a source map into a destination map.
|
|
func coalesceTables(dst, src map[string]interface{}) map[string]interface{} {
|
|
for key, val := range src {
|
|
if istable(val) {
|
|
if innerdst, ok := dst[key]; !ok {
|
|
dst[key] = val
|
|
} else if istable(innerdst) {
|
|
coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}))
|
|
} else {
|
|
log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
|
|
}
|
|
continue
|
|
} else if dv, ok := dst[key]; ok && istable(dv) {
|
|
log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
|
|
continue
|
|
}
|
|
dst[key] = val
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
|
|
func istable(v interface{}) bool {
|
|
_, ok := v.(map[string]interface{})
|
|
return ok
|
|
}
|