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 }