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