github.com/felipejfc/helm@v2.1.2+incompatible/pkg/chartutil/values.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "io" 22 "io/ioutil" 23 "log" 24 "strings" 25 26 "github.com/ghodss/yaml" 27 "github.com/golang/protobuf/ptypes/timestamp" 28 "k8s.io/helm/pkg/proto/hapi/chart" 29 ) 30 31 // ErrNoTable indicates that a chart does not have a matching table. 32 type ErrNoTable error 33 34 // GlobalKey is the name of the Values key that is used for storing global vars. 35 const GlobalKey = "global" 36 37 // Values represents a collection of chart values. 38 type Values map[string]interface{} 39 40 // YAML encodes the Values into a YAML string. 41 func (v Values) YAML() (string, error) { 42 b, err := yaml.Marshal(v) 43 return string(b), err 44 } 45 46 // Table gets a table (YAML subsection) from a Values object. 47 // 48 // The table is returned as a Values. 49 // 50 // Compound table names may be specified with dots: 51 // 52 // foo.bar 53 // 54 // The above will be evaluated as "The table bar inside the table 55 // foo". 56 // 57 // An ErrNoTable is returned if the table does not exist. 58 func (v Values) Table(name string) (Values, error) { 59 names := strings.Split(name, ".") 60 table := v 61 var err error 62 63 for _, n := range names { 64 table, err = tableLookup(table, n) 65 if err != nil { 66 return table, err 67 } 68 } 69 return table, err 70 } 71 72 // AsMap is a utility function for converting Values to a map[string]interface{}. 73 // 74 // It protects against nil map panics. 75 func (v Values) AsMap() map[string]interface{} { 76 if v == nil || len(v) == 0 { 77 return map[string]interface{}{} 78 } 79 return v 80 } 81 82 // Encode writes serialized Values information to the given io.Writer. 83 func (v Values) Encode(w io.Writer) error { 84 //return yaml.NewEncoder(w).Encode(v) 85 out, err := yaml.Marshal(v) 86 if err != nil { 87 return err 88 } 89 _, err = w.Write(out) 90 return err 91 } 92 93 func tableLookup(v Values, simple string) (Values, error) { 94 v2, ok := v[simple] 95 if !ok { 96 return v, ErrNoTable(fmt.Errorf("no table named %q (%v)", simple, v)) 97 } 98 if vv, ok := v2.(map[string]interface{}); ok { 99 return vv, nil 100 } 101 102 // This catches a case where a value is of type Values, but doesn't (for some 103 // reason) match the map[string]interface{}. This has been observed in the 104 // wild, and might be a result of a nil map of type Values. 105 if vv, ok := v2.(Values); ok { 106 return vv, nil 107 } 108 109 var e ErrNoTable = fmt.Errorf("no table named %q", simple) 110 return map[string]interface{}{}, e 111 } 112 113 // ReadValues will parse YAML byte data into a Values. 114 func ReadValues(data []byte) (vals Values, err error) { 115 err = yaml.Unmarshal(data, &vals) 116 if len(vals) == 0 { 117 vals = Values{} 118 } 119 return 120 } 121 122 // ReadValuesFile will parse a YAML file into a map of values. 123 func ReadValuesFile(filename string) (Values, error) { 124 data, err := ioutil.ReadFile(filename) 125 if err != nil { 126 return map[string]interface{}{}, err 127 } 128 return ReadValues(data) 129 } 130 131 // CoalesceValues coalesces all of the values in a chart (and its subcharts). 132 // 133 // Values are coalesced together using the following rules: 134 // 135 // - Values in a higher level chart always override values in a lower-level 136 // dependency chart 137 // - Scalar values and arrays are replaced, maps are merged 138 // - A chart has access to all of the variables for it, as well as all of 139 // the values destined for its dependencies. 140 func CoalesceValues(chrt *chart.Chart, vals *chart.Config) (Values, error) { 141 cvals := Values{} 142 // Parse values if not nil. We merge these at the top level because 143 // the passed-in values are in the same namespace as the parent chart. 144 if vals != nil { 145 evals, err := ReadValues([]byte(vals.Raw)) 146 if err != nil { 147 return cvals, err 148 } 149 cvals, err = coalesce(chrt, evals) 150 if err != nil { 151 return cvals, err 152 } 153 } 154 155 var err error 156 cvals, err = coalesceDeps(chrt, cvals) 157 return cvals, err 158 } 159 160 // coalesce coalesces the dest values and the chart values, giving priority to the dest values. 161 // 162 // This is a helper function for CoalesceValues. 163 func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { 164 var err error 165 dest, err = coalesceValues(ch, dest) 166 if err != nil { 167 return dest, err 168 } 169 coalesceDeps(ch, dest) 170 return dest, nil 171 } 172 173 // coalesceDeps coalesces the dependencies of the given chart. 174 func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { 175 for _, subchart := range chrt.Dependencies { 176 if c, ok := dest[subchart.Metadata.Name]; !ok { 177 // If dest doesn't already have the key, create it. 178 dest[subchart.Metadata.Name] = map[string]interface{}{} 179 } else if !istable(c) { 180 return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c) 181 } 182 if dv, ok := dest[subchart.Metadata.Name]; ok { 183 dvmap := dv.(map[string]interface{}) 184 185 // Get globals out of dest and merge them into dvmap. 186 coalesceGlobals(dvmap, dest) 187 188 var err error 189 // Now coalesce the rest of the values. 190 dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap) 191 if err != nil { 192 return dest, err 193 } 194 } 195 } 196 return dest, nil 197 } 198 199 // coalesceGlobals copies the globals out of src and merges them into dest. 200 // 201 // For convenience, returns dest. 202 func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} { 203 var dg, sg map[string]interface{} 204 205 if destglob, ok := dest[GlobalKey]; !ok { 206 dg = map[string]interface{}{} 207 } else if dg, ok = destglob.(map[string]interface{}); !ok { 208 log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) 209 return dg 210 } 211 212 if srcglob, ok := src[GlobalKey]; !ok { 213 sg = map[string]interface{}{} 214 } else if sg, ok = srcglob.(map[string]interface{}); !ok { 215 log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) 216 return dg 217 } 218 219 // EXPERIMENTAL: In the past, we have disallowed globals to test tables. This 220 // reverses that decision. It may somehow be possible to introduce a loop 221 // here, but I haven't found a way. So for the time being, let's allow 222 // tables in globals. 223 for key, val := range sg { 224 if istable(val) { 225 vv := copyMap(val.(map[string]interface{})) 226 if destv, ok := dg[key]; ok { 227 if destvmap, ok := destv.(map[string]interface{}); ok { 228 // Basically, we reverse order of coalesce here to merge 229 // top-down. 230 coalesceTables(vv, destvmap) 231 dg[key] = vv 232 continue 233 } else { 234 log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) 235 } 236 } else { 237 // Here there is no merge. We're just adding. 238 dg[key] = vv 239 } 240 } else if dv, ok := dg[key]; ok && istable(dv) { 241 // It's not clear if this condition can actually ever trigger. 242 log.Printf("key %s is table. Skipping", key) 243 continue 244 } 245 // TODO: Do we need to do any additional checking on the value? 246 dg[key] = val 247 } 248 dest[GlobalKey] = dg 249 return dest 250 } 251 252 func copyMap(src map[string]interface{}) map[string]interface{} { 253 dest := make(map[string]interface{}, len(src)) 254 for k, v := range src { 255 dest[k] = v 256 } 257 return dest 258 } 259 260 // coalesceValues builds up a values map for a particular chart. 261 // 262 // Values in v will override the values in the chart. 263 func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) { 264 // If there are no values in the chart, we just return the given values 265 if c.Values == nil || c.Values.Raw == "" { 266 return v, nil 267 } 268 269 nv, err := ReadValues([]byte(c.Values.Raw)) 270 if err != nil { 271 // On error, we return just the overridden values. 272 // FIXME: We should log this error. It indicates that the YAML data 273 // did not parse. 274 return v, fmt.Errorf("error reading default values (%s): %s", c.Values.Raw, err) 275 } 276 277 for key, val := range nv { 278 if _, ok := v[key]; !ok { 279 // If the key is not in v, copy it from nv. 280 v[key] = val 281 } else if dest, ok := v[key].(map[string]interface{}); ok { 282 // if v[key] is a table, merge nv's val table into v[key]. 283 src, ok := val.(map[string]interface{}) 284 if !ok { 285 log.Printf("warning: skipped value for %s: Not a table.", key) 286 continue 287 } 288 // Because v has higher precedence than nv, dest values override src 289 // values. 290 coalesceTables(dest, src) 291 } 292 } 293 return v, nil 294 } 295 296 // coalesceTables merges a source map into a destination map. 297 // 298 // dest is considered authoritative. 299 func coalesceTables(dst, src map[string]interface{}) map[string]interface{} { 300 // Because dest has higher precedence than src, dest values override src 301 // values. 302 for key, val := range src { 303 if istable(val) { 304 if innerdst, ok := dst[key]; !ok { 305 dst[key] = val 306 } else if istable(innerdst) { 307 coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{})) 308 } else { 309 log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val) 310 } 311 continue 312 } else if dv, ok := dst[key]; ok && istable(dv) { 313 log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val) 314 continue 315 } else if !ok { // <- ok is still in scope from preceding conditional. 316 dst[key] = val 317 continue 318 } 319 } 320 return dst 321 } 322 323 // ReleaseOptions represents the additional release options needed 324 // for the composition of the final values struct 325 type ReleaseOptions struct { 326 Name string 327 Time *timestamp.Timestamp 328 Namespace string 329 } 330 331 // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files 332 func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) { 333 334 top := map[string]interface{}{ 335 "Release": map[string]interface{}{ 336 "Name": options.Name, 337 "Time": options.Time, 338 "Namespace": options.Namespace, 339 "Service": "Tiller", 340 }, 341 "Chart": chrt.Metadata, 342 "Files": NewFiles(chrt.Files), 343 } 344 345 vals, err := CoalesceValues(chrt, chrtVals) 346 if err != nil { 347 return top, err 348 } 349 350 top["Values"] = vals 351 return top, nil 352 } 353 354 // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. 355 func istable(v interface{}) bool { 356 _, ok := v.(map[string]interface{}) 357 return ok 358 }