github.com/codefresh-io/kcfi@v0.0.0-20230301195427-c1578715cc46/pkg/helm-internal/resolver/resolver.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 resolver 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "os" 22 "path/filepath" 23 "strings" 24 "time" 25 26 "github.com/Masterminds/semver/v3" 27 "github.com/pkg/errors" 28 29 "helm.sh/helm/v3/pkg/chart" 30 "helm.sh/helm/v3/pkg/helmpath" 31 "helm.sh/helm/v3/pkg/provenance" 32 "helm.sh/helm/v3/pkg/repo" 33 ) 34 35 // Resolver resolves dependencies from semantic version ranges to a particular version. 36 type Resolver struct { 37 chartpath string 38 cachepath string 39 } 40 41 // New creates a new resolver for a given chart and a given helm home. 42 func New(chartpath, cachepath string) *Resolver { 43 return &Resolver{ 44 chartpath: chartpath, 45 cachepath: cachepath, 46 } 47 } 48 49 // Resolve resolves dependencies and returns a lock file with the resolution. 50 func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { 51 52 // Now we clone the dependencies, locking as we go. 53 locked := make([]*chart.Dependency, len(reqs)) 54 missing := []string{} 55 for i, d := range reqs { 56 if d.Repository == "" { 57 // Local chart subfolder 58 if _, err := GetLocalPath(filepath.Join("charts", d.Name), r.chartpath); err != nil { 59 return nil, err 60 } 61 62 locked[i] = &chart.Dependency{ 63 Name: d.Name, 64 Repository: "", 65 Version: d.Version, 66 } 67 continue 68 } 69 if strings.HasPrefix(d.Repository, "file://") { 70 71 if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil { 72 return nil, err 73 } 74 75 locked[i] = &chart.Dependency{ 76 Name: d.Name, 77 Repository: d.Repository, 78 Version: d.Version, 79 } 80 continue 81 } 82 constraint, err := semver.NewConstraint(d.Version) 83 if err != nil { 84 return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) 85 } 86 87 repoName := repoNames[d.Name] 88 // if the repository was not defined, but the dependency defines a repository url, bypass the cache 89 if repoName == "" && d.Repository != "" { 90 locked[i] = &chart.Dependency{ 91 Name: d.Name, 92 Repository: d.Repository, 93 Version: d.Version, 94 } 95 continue 96 } 97 98 repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) 99 if err != nil { 100 return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) 101 } 102 103 vs, ok := repoIndex.Entries[d.Name] 104 if !ok { 105 return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository) 106 } 107 108 locked[i] = &chart.Dependency{ 109 Name: d.Name, 110 Repository: d.Repository, 111 } 112 found := false 113 // The version are already sorted and hence the first one to satisfy the constraint is used 114 for _, ver := range vs { 115 v, err := semver.NewVersion(ver.Version) 116 if err != nil || len(ver.URLs) == 0 { 117 // Not a legit entry. 118 continue 119 } 120 if constraint.Check(v) { 121 found = true 122 locked[i].Version = v.Original() 123 break 124 } 125 } 126 127 if !found { 128 missing = append(missing, d.Name) 129 } 130 } 131 if len(missing) > 0 { 132 return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", ")) 133 } 134 135 digest, err := HashReq(reqs, locked) 136 if err != nil { 137 return nil, err 138 } 139 140 return &chart.Lock{ 141 Generated: time.Now(), 142 Digest: digest, 143 Dependencies: locked, 144 }, nil 145 } 146 147 // HashReq generates a hash of the dependencies. 148 // 149 // This should be used only to compare against another hash generated by this 150 // function. 151 func HashReq(req, lock []*chart.Dependency) (string, error) { 152 data, err := json.Marshal([2][]*chart.Dependency{req, lock}) 153 if err != nil { 154 return "", err 155 } 156 s, err := provenance.Digest(bytes.NewBuffer(data)) 157 return "sha256:" + s, err 158 } 159 160 // HashV2Req generates a hash of requirements generated in Helm v2. 161 // 162 // This should be used only to compare against another hash generated by the 163 // Helm v2 hash function. It is to handle issue: 164 // https://github.com/helm/helm/issues/7233 165 func HashV2Req(req []*chart.Dependency) (string, error) { 166 dep := make(map[string][]*chart.Dependency) 167 dep["dependencies"] = req 168 data, err := json.Marshal(dep) 169 if err != nil { 170 return "", err 171 } 172 s, err := provenance.Digest(bytes.NewBuffer(data)) 173 return "sha256:" + s, err 174 } 175 176 // GetLocalPath generates absolute local path when use 177 // "file://" in repository of dependencies 178 func GetLocalPath(repo, chartpath string) (string, error) { 179 var depPath string 180 var err error 181 p := strings.TrimPrefix(repo, "file://") 182 183 // root path is absolute 184 if strings.HasPrefix(p, "/") { 185 if depPath, err = filepath.Abs(p); err != nil { 186 return "", err 187 } 188 } else { 189 depPath = filepath.Join(chartpath, p) 190 } 191 192 if _, err = os.Stat(depPath); os.IsNotExist(err) { 193 return "", errors.Errorf("directory %s not found", depPath) 194 } else if err != nil { 195 return "", err 196 } 197 198 return depPath, nil 199 }