github.com/doitroot/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 }