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