|
|
|
/*
|
|
|
|
Copyright The Helm Authors.
|
|
|
|
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"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Masterminds/semver"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
|
|
"helm.sh/helm/pkg/chart"
|
|
|
|
"helm.sh/helm/pkg/helmpath"
|
|
|
|
"helm.sh/helm/pkg/provenance"
|
|
|
|
"helm.sh/helm/pkg/repo"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Resolver resolves dependencies from semantic version ranges to a particular version.
|
|
|
|
type Resolver struct {
|
|
|
|
chartpath string
|
|
|
|
helmhome helmpath.Home
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new resolver for a given chart and a given helm home.
|
|
|
|
func New(chartpath string, helmhome helmpath.Home) *Resolver {
|
|
|
|
return &Resolver{
|
|
|
|
chartpath: chartpath,
|
|
|
|
helmhome: helmhome,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve resolves dependencies and returns a lock file with the resolution.
|
|
|
|
func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string, d string) (*chart.Lock, error) {
|
|
|
|
|
|
|
|
// Now we clone the dependencies, locking as we go.
|
|
|
|
locked := make([]*chart.Dependency, len(reqs))
|
|
|
|
missing := []string{}
|
|
|
|
for i, d := range reqs {
|
|
|
|
if strings.HasPrefix(d.Repository, "file://") {
|
|
|
|
|
|
|
|
if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
locked[i] = &chart.Dependency{
|
|
|
|
Name: d.Name,
|
|
|
|
Repository: d.Repository,
|
|
|
|
Version: d.Version,
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
constraint, err := semver.NewConstraint(d.Version)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name]))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
|
|
|
|
}
|
|
|
|
|
|
|
|
vs, ok := repoIndex.Entries[d.Name]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
|
|
|
|
}
|
|
|
|
|
|
|
|
locked[i] = &chart.Dependency{
|
|
|
|
Name: d.Name,
|
|
|
|
Repository: d.Repository,
|
|
|
|
}
|
|
|
|
found := false
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
missing = append(missing, d.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(missing) > 0 {
|
|
|
|
return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", "))
|
|
|
|
}
|
|
|
|
return &chart.Lock{
|
|
|
|
Generated: time.Now(),
|
|
|
|
Digest: d,
|
|
|
|
Dependencies: locked,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HashReq generates a hash of the dependencies.
|
|
|
|
//
|
|
|
|
// This should be used only to compare against another hash generated by this
|
|
|
|
// function.
|
|
|
|
func HashReq(req []*chart.Dependency) (string, error) {
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
s, err := provenance.Digest(bytes.NewBuffer(data))
|
|
|
|
return "sha256:" + s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLocalPath generates absolute local path when use
|
|
|
|
// "file://" in repository of dependencies
|
|
|
|
func GetLocalPath(repo, chartpath string) (string, error) {
|
|
|
|
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) {
|
|
|
|
return "", errors.Errorf("directory %s not found", depPath)
|
|
|
|
} else if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return depPath, nil
|
|
|
|
}
|