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