github.com/qsis/helm@v3.0.0-beta.3+incompatible/pkg/chartutil/coalesce.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  	"log"
    21  
    22  	"github.com/pkg/errors"
    23  
    24  	"helm.sh/helm/pkg/chart"
    25  )
    26  
    27  // CoalesceValues coalesces all of the values in a chart (and its subcharts).
    28  //
    29  // Values are coalesced together using the following rules:
    30  //
    31  //	- Values in a higher level chart always override values in a lower-level
    32  //		dependency chart
    33  //	- Scalar values and arrays are replaced, maps are merged
    34  //	- A chart has access to all of the variables for it, as well as all of
    35  //		the values destined for its dependencies.
    36  func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) {
    37  	if vals == nil {
    38  		vals = make(map[string]interface{})
    39  	}
    40  	if _, err := coalesce(chrt, vals); err != nil {
    41  		return vals, err
    42  	}
    43  	return coalesceDeps(chrt, vals)
    44  }
    45  
    46  // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
    47  //
    48  // This is a helper function for CoalesceValues.
    49  func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
    50  	coalesceValues(ch, dest)
    51  	return coalesceDeps(ch, dest)
    52  }
    53  
    54  // coalesceDeps coalesces the dependencies of the given chart.
    55  func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
    56  	for _, subchart := range chrt.Dependencies() {
    57  		if c, ok := dest[subchart.Name()]; !ok {
    58  			// If dest doesn't already have the key, create it.
    59  			dest[subchart.Name()] = make(map[string]interface{})
    60  		} else if !istable(c) {
    61  			return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c)
    62  		}
    63  		if dv, ok := dest[subchart.Name()]; ok {
    64  			dvmap := dv.(map[string]interface{})
    65  
    66  			// Get globals out of dest and merge them into dvmap.
    67  			coalesceGlobals(dvmap, dest)
    68  
    69  			// Now coalesce the rest of the values.
    70  			var err error
    71  			dest[subchart.Name()], err = coalesce(subchart, dvmap)
    72  			if err != nil {
    73  				return dest, err
    74  			}
    75  		}
    76  	}
    77  	return dest, nil
    78  }
    79  
    80  // coalesceGlobals copies the globals out of src and merges them into dest.
    81  //
    82  // For convenience, returns dest.
    83  func coalesceGlobals(dest, src map[string]interface{}) {
    84  	var dg, sg map[string]interface{}
    85  
    86  	if destglob, ok := dest[GlobalKey]; !ok {
    87  		dg = make(map[string]interface{})
    88  	} else if dg, ok = destglob.(map[string]interface{}); !ok {
    89  		log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
    90  		return
    91  	}
    92  
    93  	if srcglob, ok := src[GlobalKey]; !ok {
    94  		sg = make(map[string]interface{})
    95  	} else if sg, ok = srcglob.(map[string]interface{}); !ok {
    96  		log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
    97  		return
    98  	}
    99  
   100  	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
   101  	// reverses that decision. It may somehow be possible to introduce a loop
   102  	// here, but I haven't found a way. So for the time being, let's allow
   103  	// tables in globals.
   104  	for key, val := range sg {
   105  		if istable(val) {
   106  			vv := copyMap(val.(map[string]interface{}))
   107  			if destv, ok := dg[key]; !ok {
   108  				// Here there is no merge. We're just adding.
   109  				dg[key] = vv
   110  			} else {
   111  				if destvmap, ok := destv.(map[string]interface{}); !ok {
   112  					log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
   113  				} else {
   114  					// Basically, we reverse order of coalesce here to merge
   115  					// top-down.
   116  					CoalesceTables(vv, destvmap)
   117  					dg[key] = vv
   118  					continue
   119  				}
   120  			}
   121  		} else if dv, ok := dg[key]; ok && istable(dv) {
   122  			// It's not clear if this condition can actually ever trigger.
   123  			log.Printf("key %s is table. Skipping", key)
   124  			continue
   125  		}
   126  		// TODO: Do we need to do any additional checking on the value?
   127  		dg[key] = val
   128  	}
   129  	dest[GlobalKey] = dg
   130  }
   131  
   132  func copyMap(src map[string]interface{}) map[string]interface{} {
   133  	m := make(map[string]interface{}, len(src))
   134  	for k, v := range src {
   135  		m[k] = v
   136  	}
   137  	return m
   138  }
   139  
   140  // coalesceValues builds up a values map for a particular chart.
   141  //
   142  // Values in v will override the values in the chart.
   143  func coalesceValues(c *chart.Chart, v map[string]interface{}) {
   144  	for key, val := range c.Values {
   145  		if value, ok := v[key]; ok {
   146  			if value == nil {
   147  				// When the YAML value is null, we remove the value's key.
   148  				// This allows Helm's various sources of values (value files or --set) to
   149  				// remove incompatible keys from any previous chart, file, or set values.
   150  				delete(v, key)
   151  			} else if dest, ok := value.(map[string]interface{}); ok {
   152  				// if v[key] is a table, merge nv's val table into v[key].
   153  				src, ok := val.(map[string]interface{})
   154  				if !ok {
   155  					log.Printf("warning: skipped value for %s: Not a table.", key)
   156  					continue
   157  				}
   158  				// Because v has higher precedence than nv, dest values override src
   159  				// values.
   160  				CoalesceTables(dest, src)
   161  			}
   162  		} else {
   163  			// If the key is not in v, copy it from nv.
   164  			v[key] = val
   165  		}
   166  	}
   167  }
   168  
   169  // CoalesceTables merges a source map into a destination map.
   170  //
   171  // dest is considered authoritative.
   172  func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
   173  	if dst == nil || src == nil {
   174  		return src
   175  	}
   176  	// Because dest has higher precedence than src, dest values override src
   177  	// values.
   178  	for key, val := range src {
   179  		if istable(val) {
   180  			switch innerdst, ok := dst[key]; {
   181  			case !ok:
   182  				dst[key] = val
   183  			case istable(innerdst):
   184  				CoalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}))
   185  			default:
   186  				log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
   187  			}
   188  		} else if dv, ok := dst[key]; ok && istable(dv) {
   189  			log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
   190  		} else if !ok { // <- ok is still in scope from preceding conditional.
   191  			dst[key] = val
   192  		}
   193  	}
   194  	return dst
   195  }