github.com/cloudposse/helm@v2.2.3+incompatible/pkg/chartutil/requirements.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "errors" 20 "log" 21 "strings" 22 "time" 23 24 "github.com/ghodss/yaml" 25 "k8s.io/helm/pkg/proto/hapi/chart" 26 ) 27 28 const ( 29 requirementsName = "requirements.yaml" 30 lockfileName = "requirements.lock" 31 ) 32 33 var ( 34 // ErrRequirementsNotFound indicates that a requirements.yaml is not found. 35 ErrRequirementsNotFound = errors.New(requirementsName + " not found") 36 // ErrLockfileNotFound indicates that a requirements.lock is not found. 37 ErrLockfileNotFound = errors.New(lockfileName + " not found") 38 ) 39 40 // Dependency describes a chart upon which another chart depends. 41 // 42 // Dependencies can be used to express developer intent, or to capture the state 43 // of a chart. 44 type Dependency struct { 45 // Name is the name of the dependency. 46 // 47 // This must mach the name in the dependency's Chart.yaml. 48 Name string `json:"name"` 49 // Version is the version (range) of this chart. 50 // 51 // A lock file will always produce a single version, while a dependency 52 // may contain a semantic version range. 53 Version string `json:"version,omitempty"` 54 // The URL to the repository. 55 // 56 // Appending `index.yaml` to this string should result in a URL that can be 57 // used to fetch the repository index. 58 Repository string `json:"repository"` 59 // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) 60 Condition string `json:"condition"` 61 // Tags can be used to group charts for enabling/disabling together 62 Tags []string `json:"tags"` 63 // Enabled bool determines if chart should be loaded 64 Enabled bool `json:"enabled"` 65 } 66 67 // ErrNoRequirementsFile to detect error condition 68 type ErrNoRequirementsFile error 69 70 // Requirements is a list of requirements for a chart. 71 // 72 // Requirements are charts upon which this chart depends. This expresses 73 // developer intent. 74 type Requirements struct { 75 Dependencies []*Dependency `json:"dependencies"` 76 } 77 78 // RequirementsLock is a lock file for requirements. 79 // 80 // It represents the state that the dependencies should be in. 81 type RequirementsLock struct { 82 // Genderated is the date the lock file was last generated. 83 Generated time.Time `json:"generated"` 84 // Digest is a hash of the requirements file used to generate it. 85 Digest string `json:"digest"` 86 // Dependencies is the list of dependencies that this lock file has locked. 87 Dependencies []*Dependency `json:"dependencies"` 88 } 89 90 // LoadRequirements loads a requirements file from an in-memory chart. 91 func LoadRequirements(c *chart.Chart) (*Requirements, error) { 92 var data []byte 93 for _, f := range c.Files { 94 if f.TypeUrl == requirementsName { 95 data = f.Value 96 } 97 } 98 if len(data) == 0 { 99 return nil, ErrRequirementsNotFound 100 } 101 r := &Requirements{} 102 return r, yaml.Unmarshal(data, r) 103 } 104 105 // LoadRequirementsLock loads a requirements lock file. 106 func LoadRequirementsLock(c *chart.Chart) (*RequirementsLock, error) { 107 var data []byte 108 for _, f := range c.Files { 109 if f.TypeUrl == lockfileName { 110 data = f.Value 111 } 112 } 113 if len(data) == 0 { 114 return nil, ErrLockfileNotFound 115 } 116 r := &RequirementsLock{} 117 return r, yaml.Unmarshal(data, r) 118 } 119 120 // ProcessRequirementsConditions disables charts based on condition path value in values 121 func ProcessRequirementsConditions(reqs *Requirements, cvals Values) { 122 var cond string 123 var conds []string 124 if reqs == nil || len(reqs.Dependencies) == 0 { 125 return 126 } 127 for _, r := range reqs.Dependencies { 128 var hasTrue, hasFalse bool 129 cond = string(r.Condition) 130 // check for list 131 if len(cond) > 0 { 132 if strings.Contains(cond, ",") { 133 conds = strings.Split(strings.TrimSpace(cond), ",") 134 } else { 135 conds = []string{strings.TrimSpace(cond)} 136 } 137 for _, c := range conds { 138 if len(c) > 0 { 139 // retrieve value 140 vv, err := cvals.PathValue(c) 141 if err == nil { 142 // if not bool, warn 143 if bv, ok := vv.(bool); ok { 144 if bv { 145 hasTrue = true 146 } else { 147 hasFalse = true 148 } 149 } else { 150 log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) 151 } 152 } else if _, ok := err.(ErrNoValue); !ok { 153 // this is a real error 154 log.Printf("Warning: PathValue returned error %v", err) 155 156 } 157 if vv != nil { 158 // got first value, break loop 159 break 160 } 161 } 162 } 163 if !hasTrue && hasFalse { 164 r.Enabled = false 165 } else if hasTrue { 166 r.Enabled = true 167 168 } 169 } 170 171 } 172 173 } 174 175 // ProcessRequirementsTags disables charts based on tags in values 176 func ProcessRequirementsTags(reqs *Requirements, cvals Values) { 177 vt, err := cvals.Table("tags") 178 if err != nil { 179 return 180 181 } 182 if reqs == nil || len(reqs.Dependencies) == 0 { 183 return 184 } 185 for _, r := range reqs.Dependencies { 186 if len(r.Tags) > 0 { 187 tags := r.Tags 188 189 var hasTrue, hasFalse bool 190 for _, k := range tags { 191 if b, ok := vt[k]; ok { 192 // if not bool, warn 193 if bv, ok := b.(bool); ok { 194 if bv { 195 hasTrue = true 196 } else { 197 hasFalse = true 198 } 199 } else { 200 log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) 201 } 202 } 203 } 204 if !hasTrue && hasFalse { 205 r.Enabled = false 206 } else if hasTrue || !hasTrue && !hasFalse { 207 r.Enabled = true 208 209 } 210 211 } 212 } 213 214 } 215 216 // ProcessRequirementsEnabled removes disabled charts from dependencies 217 func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { 218 reqs, err := LoadRequirements(c) 219 if err != nil { 220 // if not just missing requirements file, return error 221 if nerr, ok := err.(ErrNoRequirementsFile); !ok { 222 return nerr 223 } 224 225 // no requirements to process 226 return nil 227 } 228 // set all to true 229 for _, lr := range reqs.Dependencies { 230 lr.Enabled = true 231 } 232 cvals, err := CoalesceValues(c, v) 233 if err != nil { 234 return err 235 } 236 // flag dependencies as enabled/disabled 237 ProcessRequirementsTags(reqs, cvals) 238 ProcessRequirementsConditions(reqs, cvals) 239 240 // make a map of charts to remove 241 rm := map[string]bool{} 242 for _, r := range reqs.Dependencies { 243 if !r.Enabled { 244 // remove disabled chart 245 rm[r.Name] = true 246 } 247 } 248 // don't keep disabled charts in new slice 249 cd := []*chart.Chart{} 250 copy(cd, c.Dependencies[:0]) 251 for _, n := range c.Dependencies { 252 if _, ok := rm[n.Metadata.Name]; !ok { 253 cd = append(cd, n) 254 } 255 256 } 257 // recursively call self to process sub dependencies 258 for _, t := range cd { 259 err := ProcessRequirementsEnabled(t, v) 260 // if its not just missing requirements file, return error 261 if nerr, ok := err.(ErrNoRequirementsFile); !ok && err != nil { 262 return nerr 263 } 264 } 265 c.Dependencies = cd 266 267 return nil 268 }