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