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