github.com/x-helm/helm@v3.0.0-beta.3+incompatible/pkg/chartutil/dependencies.go (about) 1 /* 2 Copyright The Helm Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package chartutil 17 18 import ( 19 "log" 20 "strings" 21 22 "helm.sh/helm/pkg/chart" 23 ) 24 25 // ProcessDependencies checks through this chart's dependencies, processing accordingly. 26 func ProcessDependencies(c *chart.Chart, v Values) error { 27 if err := processDependencyEnabled(c, v); err != nil { 28 return err 29 } 30 return processDependencyImportValues(c) 31 } 32 33 // processDependencyConditions disables charts based on condition path value in values 34 func processDependencyConditions(reqs []*chart.Dependency, cvals Values) { 35 if reqs == nil { 36 return 37 } 38 for _, r := range reqs { 39 var hasTrue, hasFalse bool 40 for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") { 41 if len(c) > 0 { 42 // retrieve value 43 vv, err := cvals.PathValue(c) 44 if err == nil { 45 // if not bool, warn 46 if bv, ok := vv.(bool); ok { 47 if bv { 48 hasTrue = true 49 } else { 50 hasFalse = true 51 } 52 } else { 53 log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) 54 } 55 } else if _, ok := err.(ErrNoValue); !ok { 56 // this is a real error 57 log.Printf("Warning: PathValue returned error %v", err) 58 } 59 if vv != nil { 60 // got first value, break loop 61 break 62 } 63 } 64 } 65 if !hasTrue && hasFalse { 66 r.Enabled = false 67 } else if hasTrue { 68 r.Enabled = true 69 70 } 71 } 72 } 73 74 // processDependencyTags disables charts based on tags in values 75 func processDependencyTags(reqs []*chart.Dependency, cvals Values) { 76 if reqs == nil { 77 return 78 } 79 vt, err := cvals.Table("tags") 80 if err != nil { 81 return 82 } 83 for _, r := range reqs { 84 var hasTrue, hasFalse bool 85 for _, k := range r.Tags { 86 if b, ok := vt[k]; ok { 87 // if not bool, warn 88 if bv, ok := b.(bool); ok { 89 if bv { 90 hasTrue = true 91 } else { 92 hasFalse = true 93 } 94 } else { 95 log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) 96 } 97 } 98 } 99 if !hasTrue && hasFalse { 100 r.Enabled = false 101 } else if hasTrue || !hasTrue && !hasFalse { 102 r.Enabled = true 103 } 104 } 105 } 106 107 func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart { 108 for _, c := range charts { 109 if c == nil { 110 continue 111 } 112 if c.Name() != dep.Name { 113 continue 114 } 115 if !IsCompatibleRange(dep.Version, c.Metadata.Version) { 116 continue 117 } 118 119 out := *c 120 md := *c.Metadata 121 out.Metadata = &md 122 123 if dep.Alias != "" { 124 md.Name = dep.Alias 125 } 126 return &out 127 } 128 return nil 129 } 130 131 // processDependencyEnabled removes disabled charts from dependencies 132 func processDependencyEnabled(c *chart.Chart, v map[string]interface{}) error { 133 if c.Metadata.Dependencies == nil { 134 return nil 135 } 136 137 var chartDependencies []*chart.Chart 138 // If any dependency is not a part of Chart.yaml 139 // then this should be added to chartDependencies. 140 // However, if the dependency is already specified in Chart.yaml 141 // we should not add it, as it would be anyways processed from Chart.yaml 142 143 Loop: 144 for _, existing := range c.Dependencies() { 145 for _, req := range c.Metadata.Dependencies { 146 if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) { 147 continue Loop 148 } 149 } 150 chartDependencies = append(chartDependencies, existing) 151 } 152 153 for _, req := range c.Metadata.Dependencies { 154 if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { 155 chartDependencies = append(chartDependencies, chartDependency) 156 } 157 if req.Alias != "" { 158 req.Name = req.Alias 159 } 160 } 161 c.SetDependencies(chartDependencies...) 162 163 // set all to true 164 for _, lr := range c.Metadata.Dependencies { 165 lr.Enabled = true 166 } 167 cvals, err := CoalesceValues(c, v) 168 if err != nil { 169 return err 170 } 171 // flag dependencies as enabled/disabled 172 processDependencyTags(c.Metadata.Dependencies, cvals) 173 processDependencyConditions(c.Metadata.Dependencies, cvals) 174 // make a map of charts to remove 175 rm := map[string]struct{}{} 176 for _, r := range c.Metadata.Dependencies { 177 if !r.Enabled { 178 // remove disabled chart 179 rm[r.Name] = struct{}{} 180 } 181 } 182 // don't keep disabled charts in new slice 183 cd := []*chart.Chart{} 184 copy(cd, c.Dependencies()[:0]) 185 for _, n := range c.Dependencies() { 186 if _, ok := rm[n.Metadata.Name]; !ok { 187 cd = append(cd, n) 188 } 189 } 190 191 // recursively call self to process sub dependencies 192 for _, t := range cd { 193 if err := processDependencyEnabled(t, cvals); err != nil { 194 return err 195 } 196 } 197 c.SetDependencies(cd...) 198 199 return nil 200 } 201 202 // pathToMap creates a nested map given a YAML path in dot notation. 203 func pathToMap(path string, data map[string]interface{}) map[string]interface{} { 204 if path == "." { 205 return data 206 } 207 return set(parsePath(path), data) 208 } 209 210 func set(path []string, data map[string]interface{}) map[string]interface{} { 211 if len(path) == 0 { 212 return nil 213 } 214 cur := data 215 for i := len(path) - 1; i >= 0; i-- { 216 cur = map[string]interface{}{path[i]: cur} 217 } 218 return cur 219 } 220 221 // processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. 222 func processImportValues(c *chart.Chart) error { 223 if c.Metadata.Dependencies == nil { 224 return nil 225 } 226 // combine chart values and empty config to get Values 227 cvals, err := CoalesceValues(c, nil) 228 if err != nil { 229 return err 230 } 231 b := make(map[string]interface{}) 232 // import values from each dependency if specified in import-values 233 for _, r := range c.Metadata.Dependencies { 234 var outiv []interface{} 235 for _, riv := range r.ImportValues { 236 switch iv := riv.(type) { 237 case map[string]interface{}: 238 child := iv["child"].(string) 239 parent := iv["parent"].(string) 240 241 outiv = append(outiv, map[string]string{ 242 "child": child, 243 "parent": parent, 244 }) 245 246 // get child table 247 vv, err := cvals.Table(r.Name + "." + child) 248 if err != nil { 249 log.Printf("Warning: ImportValues missing table: %v", err) 250 continue 251 } 252 // create value map from child to be merged into parent 253 b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap())) 254 case string: 255 child := "exports." + iv 256 outiv = append(outiv, map[string]string{ 257 "child": child, 258 "parent": ".", 259 }) 260 vm, err := cvals.Table(r.Name + "." + child) 261 if err != nil { 262 log.Printf("Warning: ImportValues missing table: %v", err) 263 continue 264 } 265 b = CoalesceTables(b, vm.AsMap()) 266 } 267 } 268 // set our formatted import values 269 r.ImportValues = outiv 270 } 271 272 // set the new values 273 c.Values = CoalesceTables(b, cvals) 274 275 return nil 276 } 277 278 // processDependencyImportValues imports specified chart values from child to parent. 279 func processDependencyImportValues(c *chart.Chart) error { 280 for _, d := range c.Dependencies() { 281 // recurse 282 if err := processDependencyImportValues(d); err != nil { 283 return err 284 } 285 } 286 return processImportValues(c) 287 }