github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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 = coalesce(chrt, evals) 150 } 151 152 cvals = coalesceDeps(chrt, cvals) 153 154 return cvals, nil 155 } 156 157 // coalesce coalesces the dest values and the chart values, giving priority to the dest values. 158 // 159 // This is a helper function for CoalesceValues. 160 func coalesce(ch *chart.Chart, dest map[string]interface{}) map[string]interface{} { 161 dest = coalesceValues(ch, dest) 162 coalesceDeps(ch, dest) 163 return dest 164 } 165 166 // coalesceDeps coalesces the dependencies of the given chart. 167 func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) map[string]interface{} { 168 for _, subchart := range chrt.Dependencies { 169 if c, ok := dest[subchart.Metadata.Name]; !ok { 170 // If dest doesn't already have the key, create it. 171 dest[subchart.Metadata.Name] = map[string]interface{}{} 172 } else if !istable(c) { 173 log.Printf("error: type mismatch on %s: %t", subchart.Metadata.Name, c) 174 return dest 175 } 176 if dv, ok := dest[subchart.Metadata.Name]; ok { 177 dvmap := dv.(map[string]interface{}) 178 179 // Get globals out of dest and merge them into dvmap. 180 coalesceGlobals(dvmap, dest) 181 182 // Now coalesce the rest of the values. 183 dest[subchart.Metadata.Name] = coalesce(subchart, dvmap) 184 } 185 } 186 return dest 187 } 188 189 // coalesceGlobals copies the globals out of src and merges them into dest. 190 // 191 // For convenience, returns dest. 192 func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} { 193 var dg, sg map[string]interface{} 194 195 if destglob, ok := dest[GlobalKey]; !ok { 196 dg = map[string]interface{}{} 197 } else if dg, ok = destglob.(map[string]interface{}); !ok { 198 log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) 199 return dg 200 } 201 202 if srcglob, ok := src[GlobalKey]; !ok { 203 sg = map[string]interface{}{} 204 } else if sg, ok = srcglob.(map[string]interface{}); !ok { 205 log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) 206 return dg 207 } 208 209 // We manually copy (instead of using coalesceTables) because (a) we need 210 // to prevent loops, and (b) we disallow nesting tables under globals. 211 // Globals should _just_ be k/v pairs. 212 for key, val := range sg { 213 if istable(val) { 214 log.Printf("warning: nested values are illegal in globals (%s)", key) 215 continue 216 } else if dv, ok := dg[key]; ok && istable(dv) { 217 log.Printf("warning: nested values are illegal in globals (%s)", key) 218 continue 219 } 220 // TODO: Do we need to do any additional checking on the value? 221 dg[key] = val 222 } 223 dest[GlobalKey] = dg 224 return dest 225 226 } 227 228 // coalesceValues builds up a values map for a particular chart. 229 // 230 // Values in v will override the values in the chart. 231 func coalesceValues(c *chart.Chart, v map[string]interface{}) map[string]interface{} { 232 // If there are no values in the chart, we just return the given values 233 if c.Values == nil || c.Values.Raw == "" { 234 return v 235 } 236 237 nv, err := ReadValues([]byte(c.Values.Raw)) 238 if err != nil { 239 // On error, we return just the overridden values. 240 // FIXME: We should log this error. It indicates that the YAML data 241 // did not parse. 242 log.Printf("error reading default values (%s): %s", c.Values.Raw, err) 243 return v 244 } 245 246 for key, val := range nv { 247 if _, ok := v[key]; !ok { 248 // If the key is not in v, copy it from nv. 249 v[key] = val 250 } else if dest, ok := v[key].(map[string]interface{}); ok { 251 // if v[key] is a table, merge nv's val table into v[key]. 252 src, ok := val.(map[string]interface{}) 253 if !ok { 254 log.Printf("warning: skipped value for %s: Not a table.", key) 255 continue 256 } 257 // Because v has higher precedence than nv, dest values override src 258 // values. 259 coalesceTables(dest, src) 260 } 261 } 262 return v 263 } 264 265 // coalesceTables merges a source map into a destination map. 266 // 267 // dest is considered authoritative. 268 func coalesceTables(dst, src map[string]interface{}) map[string]interface{} { 269 // Because dest has higher precedence than src, dest values override src 270 // values. 271 for key, val := range src { 272 if istable(val) { 273 if innerdst, ok := dst[key]; !ok { 274 dst[key] = val 275 } else if istable(innerdst) { 276 coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{})) 277 } else { 278 log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val) 279 } 280 continue 281 } else if dv, ok := dst[key]; ok && istable(dv) { 282 log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val) 283 continue 284 } else if !ok { // <- ok is still in scope from preceding conditional. 285 dst[key] = val 286 continue 287 } 288 } 289 return dst 290 } 291 292 // ReleaseOptions represents the additional release options needed 293 // for the composition of the final values struct 294 type ReleaseOptions struct { 295 Name string 296 Time *timestamp.Timestamp 297 Namespace string 298 } 299 300 // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files 301 func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) { 302 303 top := map[string]interface{}{ 304 "Release": map[string]interface{}{ 305 "Name": options.Name, 306 "Time": options.Time, 307 "Namespace": options.Namespace, 308 "Service": "Tiller", 309 }, 310 "Chart": chrt.Metadata, 311 "Files": NewFiles(chrt.Files), 312 } 313 314 vals, err := CoalesceValues(chrt, chrtVals) 315 if err != nil { 316 return top, err 317 } 318 319 top["Values"] = vals 320 return top, nil 321 } 322 323 // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. 324 func istable(v interface{}) bool { 325 _, ok := v.(map[string]interface{}) 326 return ok 327 }