github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/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 "fmt" 21 "io" 22 "io/ioutil" 23 "strings" 24 25 "github.com/pkg/errors" 26 "sigs.k8s.io/yaml" 27 28 "github.com/stefanmcshane/helm/pkg/chart" 29 ) 30 31 // GlobalKey is the name of the Values key that is used for storing global vars. 32 const GlobalKey = "global" 33 34 // Values represents a collection of chart values. 35 type Values map[string]interface{} 36 37 // YAML encodes the Values into a YAML string. 38 func (v Values) YAML() (string, error) { 39 b, err := yaml.Marshal(v) 40 return string(b), err 41 } 42 43 // Table gets a table (YAML subsection) from a Values object. 44 // 45 // The table is returned as a Values. 46 // 47 // Compound table names may be specified with dots: 48 // 49 // foo.bar 50 // 51 // The above will be evaluated as "The table bar inside the table 52 // foo". 53 // 54 // An ErrNoTable is returned if the table does not exist. 55 func (v Values) Table(name string) (Values, error) { 56 table := v 57 var err error 58 59 for _, n := range parsePath(name) { 60 if table, err = tableLookup(table, n); err != nil { 61 break 62 } 63 } 64 return table, err 65 } 66 67 // AsMap is a utility function for converting Values to a map[string]interface{}. 68 // 69 // It protects against nil map panics. 70 func (v Values) AsMap() map[string]interface{} { 71 if len(v) == 0 { 72 return map[string]interface{}{} 73 } 74 return v 75 } 76 77 // Encode writes serialized Values information to the given io.Writer. 78 func (v Values) Encode(w io.Writer) error { 79 out, err := yaml.Marshal(v) 80 if err != nil { 81 return err 82 } 83 _, err = w.Write(out) 84 return err 85 } 86 87 func tableLookup(v Values, simple string) (Values, error) { 88 v2, ok := v[simple] 89 if !ok { 90 return v, ErrNoTable{simple} 91 } 92 if vv, ok := v2.(map[string]interface{}); ok { 93 return vv, nil 94 } 95 96 // This catches a case where a value is of type Values, but doesn't (for some 97 // reason) match the map[string]interface{}. This has been observed in the 98 // wild, and might be a result of a nil map of type Values. 99 if vv, ok := v2.(Values); ok { 100 return vv, nil 101 } 102 103 return Values{}, ErrNoTable{simple} 104 } 105 106 // ReadValues will parse YAML byte data into a Values. 107 func ReadValues(data []byte) (vals Values, err error) { 108 err = yaml.Unmarshal(data, &vals) 109 if len(vals) == 0 { 110 vals = Values{} 111 } 112 return vals, err 113 } 114 115 // ReadValuesFile will parse a YAML file into a map of values. 116 func ReadValuesFile(filename string) (Values, error) { 117 data, err := ioutil.ReadFile(filename) 118 if err != nil { 119 return map[string]interface{}{}, err 120 } 121 return ReadValues(data) 122 } 123 124 // ReleaseOptions represents the additional release options needed 125 // for the composition of the final values struct 126 type ReleaseOptions struct { 127 Name string 128 Namespace string 129 Revision int 130 IsUpgrade bool 131 IsInstall bool 132 } 133 134 // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files 135 // 136 // This takes both ReleaseOptions and Capabilities to merge into the render values. 137 func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) { 138 if caps == nil { 139 caps = DefaultCapabilities 140 } 141 top := map[string]interface{}{ 142 "Chart": chrt.Metadata, 143 "Capabilities": caps, 144 "Release": map[string]interface{}{ 145 "Name": options.Name, 146 "Namespace": options.Namespace, 147 "IsUpgrade": options.IsUpgrade, 148 "IsInstall": options.IsInstall, 149 "Revision": options.Revision, 150 "Service": "Helm", 151 }, 152 } 153 154 vals, err := CoalesceValues(chrt, chrtVals) 155 if err != nil { 156 return top, err 157 } 158 159 if err := ValidateAgainstSchema(chrt, vals); err != nil { 160 errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" 161 return top, fmt.Errorf(errFmt, err.Error()) 162 } 163 164 top["Values"] = vals 165 return top, nil 166 } 167 168 // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. 169 func istable(v interface{}) bool { 170 _, ok := v.(map[string]interface{}) 171 return ok 172 } 173 174 // PathValue takes a path that traverses a YAML structure and returns the value at the end of that path. 175 // The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods. 176 // Given the following YAML data the value at path "chapter.one.title" is "Loomings". 177 // 178 // chapter: 179 // one: 180 // title: "Loomings" 181 func (v Values) PathValue(path string) (interface{}, error) { 182 if path == "" { 183 return nil, errors.New("YAML path cannot be empty") 184 } 185 return v.pathValue(parsePath(path)) 186 } 187 188 func (v Values) pathValue(path []string) (interface{}, error) { 189 if len(path) == 1 { 190 // if exists must be root key not table 191 if _, ok := v[path[0]]; ok && !istable(v[path[0]]) { 192 return v[path[0]], nil 193 } 194 return nil, ErrNoValue{path[0]} 195 } 196 197 key, path := path[len(path)-1], path[:len(path)-1] 198 // get our table for table path 199 t, err := v.Table(joinPath(path...)) 200 if err != nil { 201 return nil, ErrNoValue{key} 202 } 203 // check table for key and ensure value is not a table 204 if k, ok := t[key]; ok && !istable(k) { 205 return k, nil 206 } 207 return nil, ErrNoValue{key} 208 } 209 210 func parsePath(key string) []string { return strings.Split(key, ".") } 211 212 func joinPath(path ...string) string { return strings.Join(path, ".") }