| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2018-08-25 03:03:55 +08:00
										 |  |  | Copyright The Helm Authors. | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | You may obtain a copy of the License at | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | See the License for the specific language governing permissions and | 
					
						
							|  |  |  | limitations under the License. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package resolver | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2017-02-10 03:43:18 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-04 02:27:05 +08:00
										 |  |  | 	"helm.sh/helm/v3/pkg/chart" | 
					
						
							| 
									
										
										
										
											2020-07-16 23:03:08 +08:00
										 |  |  | 	"helm.sh/helm/v3/pkg/chart/loader" | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 	"helm.sh/helm/v3/pkg/gates" | 
					
						
							| 
									
										
										
										
											2019-10-04 02:27:05 +08:00
										 |  |  | 	"helm.sh/helm/v3/pkg/helmpath" | 
					
						
							|  |  |  | 	"helm.sh/helm/v3/pkg/provenance" | 
					
						
							|  |  |  | 	"helm.sh/helm/v3/pkg/repo" | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/Masterminds/semver/v3" | 
					
						
							|  |  |  | 	"github.com/pkg/errors" | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | // Resolver resolves dependencies from semantic version ranges to a particular version.
 | 
					
						
							|  |  |  | type Resolver struct { | 
					
						
							|  |  |  | 	chartpath string | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | 	cachepath string | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // New creates a new resolver for a given chart and a given helm home.
 | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | func New(chartpath, cachepath string) *Resolver { | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 	return &Resolver{ | 
					
						
							|  |  |  | 		chartpath: chartpath, | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | 		cachepath: cachepath, | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Resolve resolves dependencies and returns a lock file with the resolution.
 | 
					
						
							| 
									
										
										
										
											2019-06-12 07:09:21 +08:00
										 |  |  | func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Now we clone the dependencies, locking as we go.
 | 
					
						
							| 
									
										
										
										
											2018-08-30 05:05:37 +08:00
										 |  |  | 	locked := make([]*chart.Dependency, len(reqs)) | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 	missing := []string{} | 
					
						
							| 
									
										
										
										
											2018-08-30 05:05:37 +08:00
										 |  |  | 	for i, d := range reqs { | 
					
						
							| 
									
										
										
										
											2019-10-09 23:35:55 +08:00
										 |  |  | 		if d.Repository == "" { | 
					
						
							|  |  |  | 			// Local chart subfolder
 | 
					
						
							|  |  |  | 			if _, err := GetLocalPath(filepath.Join("charts", d.Name), r.chartpath); err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			locked[i] = &chart.Dependency{ | 
					
						
							|  |  |  | 				Name:       d.Name, | 
					
						
							|  |  |  | 				Repository: "", | 
					
						
							|  |  |  | 				Version:    d.Version, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-02-10 03:43:18 +08:00
										 |  |  | 		if strings.HasPrefix(d.Repository, "file://") { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-16 23:03:08 +08:00
										 |  |  | 			chartpath, err := GetLocalPath(d.Repository, r.chartpath) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// The version of the chart locked will be the version of the chart
 | 
					
						
							|  |  |  | 			// currently listed in the file system within the chart.
 | 
					
						
							|  |  |  | 			ch, err := loader.LoadDir(chartpath) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2017-02-11 03:16:28 +08:00
										 |  |  | 				return nil, err | 
					
						
							| 
									
										
										
										
											2017-02-10 03:43:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 02:28:29 +08:00
										 |  |  | 			locked[i] = &chart.Dependency{ | 
					
						
							| 
									
										
										
										
											2017-02-10 03:43:18 +08:00
										 |  |  | 				Name:       d.Name, | 
					
						
							|  |  |  | 				Repository: d.Repository, | 
					
						
							| 
									
										
										
										
											2020-07-16 23:03:08 +08:00
										 |  |  | 				Version:    ch.Metadata.Version, | 
					
						
							| 
									
										
										
										
											2017-02-10 03:43:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 		constraint, err := semver.NewConstraint(d.Version) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2018-05-11 00:34:41 +08:00
										 |  |  | 			return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-25 01:03:30 +08:00
										 |  |  | 		repoName := repoNames[d.Name] | 
					
						
							|  |  |  | 		// if the repository was not defined, but the dependency defines a repository url, bypass the cache
 | 
					
						
							|  |  |  | 		if repoName == "" && d.Repository != "" { | 
					
						
							|  |  |  | 			locked[i] = &chart.Dependency{ | 
					
						
							|  |  |  | 				Name:       d.Name, | 
					
						
							|  |  |  | 				Repository: d.Repository, | 
					
						
							|  |  |  | 				Version:    d.Version, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 		var vs repo.ChartVersions | 
					
						
							|  |  |  | 		var version string | 
					
						
							|  |  |  | 		var ok bool | 
					
						
							|  |  |  | 		found := true | 
					
						
							|  |  |  | 		if !strings.HasPrefix(d.Repository, "oci://") { | 
					
						
							|  |  |  | 			repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 			vs, ok = repoIndex.Entries[d.Name] | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			found = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			version = d.Version | 
					
						
							|  |  |  | 			if !FeatureGateOCI.IsEnabled() { | 
					
						
							|  |  |  | 				return nil, errors.Wrapf(FeatureGateOCI.Error(), | 
					
						
							|  |  |  | 					"repository %s is an OCI registry", d.Repository) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 02:28:29 +08:00
										 |  |  | 		locked[i] = &chart.Dependency{ | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 			Name:       d.Name, | 
					
						
							|  |  |  | 			Repository: d.Repository, | 
					
						
							| 
									
										
										
										
											2020-10-02 05:37:44 +08:00
										 |  |  | 			Version:    version, | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 		// The version are already sorted and hence the first one to satisfy the constraint is used
 | 
					
						
							|  |  |  | 		for _, ver := range vs { | 
					
						
							|  |  |  | 			v, err := semver.NewVersion(ver.Version) | 
					
						
							|  |  |  | 			if err != nil || len(ver.URLs) == 0 { | 
					
						
							|  |  |  | 				// Not a legit entry.
 | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if constraint.Check(v) { | 
					
						
							|  |  |  | 				found = true | 
					
						
							|  |  |  | 				locked[i].Version = v.Original() | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 		if !found { | 
					
						
							|  |  |  | 			missing = append(missing, d.Name) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(missing) > 0 { | 
					
						
							| 
									
										
										
										
											2018-08-30 05:05:37 +08:00
										 |  |  | 		return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", ")) | 
					
						
							| 
									
										
										
										
											2016-11-16 07:50:19 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-06-12 07:09:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-22 11:38:49 +08:00
										 |  |  | 	digest, err := HashReq(reqs, locked) | 
					
						
							| 
									
										
										
										
											2019-06-12 07:09:21 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-30 05:43:37 +08:00
										 |  |  | 	return &chart.Lock{ | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 		Generated:    time.Now(), | 
					
						
							| 
									
										
										
										
											2019-06-12 07:09:21 +08:00
										 |  |  | 		Digest:       digest, | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 		Dependencies: locked, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-29 02:20:33 +08:00
										 |  |  | // HashReq generates a hash of the dependencies.
 | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // This should be used only to compare against another hash generated by this
 | 
					
						
							|  |  |  | // function.
 | 
					
						
							| 
									
										
										
										
											2019-10-22 11:38:49 +08:00
										 |  |  | func HashReq(req, lock []*chart.Dependency) (string, error) { | 
					
						
							|  |  |  | 	data, err := json.Marshal([2][]*chart.Dependency{req, lock}) | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s, err := provenance.Digest(bytes.NewBuffer(data)) | 
					
						
							|  |  |  | 	return "sha256:" + s, err | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-03-28 08:31:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 16:38:30 +08:00
										 |  |  | // HashV2Req generates a hash of requirements generated in Helm v2.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This should be used only to compare against another hash generated by the
 | 
					
						
							|  |  |  | // Helm v2 hash function. It is to handle issue:
 | 
					
						
							|  |  |  | // https://github.com/helm/helm/issues/7233
 | 
					
						
							|  |  |  | func HashV2Req(req []*chart.Dependency) (string, error) { | 
					
						
							|  |  |  | 	dep := make(map[string][]*chart.Dependency) | 
					
						
							|  |  |  | 	dep["dependencies"] = req | 
					
						
							|  |  |  | 	data, err := json.Marshal(dep) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s, err := provenance.Digest(bytes.NewBuffer(data)) | 
					
						
							|  |  |  | 	return "sha256:" + s, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-28 08:31:01 +08:00
										 |  |  | // GetLocalPath generates absolute local path when use
 | 
					
						
							| 
									
										
										
										
											2018-11-29 02:20:33 +08:00
										 |  |  | // "file://" in repository of dependencies
 | 
					
						
							| 
									
										
										
										
											2018-05-11 00:34:41 +08:00
										 |  |  | func GetLocalPath(repo, chartpath string) (string, error) { | 
					
						
							| 
									
										
										
										
											2017-03-28 08:31:01 +08:00
										 |  |  | 	var depPath string | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	p := strings.TrimPrefix(repo, "file://") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// root path is absolute
 | 
					
						
							|  |  |  | 	if strings.HasPrefix(p, "/") { | 
					
						
							|  |  |  | 		if depPath, err = filepath.Abs(p); err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		depPath = filepath.Join(chartpath, p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err = os.Stat(depPath); os.IsNotExist(err) { | 
					
						
							| 
									
										
										
										
											2018-05-11 00:34:41 +08:00
										 |  |  | 		return "", errors.Errorf("directory %s not found", depPath) | 
					
						
							| 
									
										
										
										
											2017-03-28 08:31:01 +08:00
										 |  |  | 	} else if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return depPath, nil | 
					
						
							|  |  |  | } |