...

Source file src/helm.sh/helm/v3/internal/resolver/resolver.go

Documentation: helm.sh/helm/v3/internal/resolver

     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  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/Masterminds/semver/v3"
    28  	"github.com/pkg/errors"
    29  
    30  	"helm.sh/helm/v3/pkg/chart"
    31  	"helm.sh/helm/v3/pkg/chart/loader"
    32  	"helm.sh/helm/v3/pkg/helmpath"
    33  	"helm.sh/helm/v3/pkg/provenance"
    34  	"helm.sh/helm/v3/pkg/registry"
    35  	"helm.sh/helm/v3/pkg/repo"
    36  )
    37  
    38  // Resolver resolves dependencies from semantic version ranges to a particular version.
    39  type Resolver struct {
    40  	chartpath      string
    41  	cachepath      string
    42  	registryClient *registry.Client
    43  }
    44  
    45  // New creates a new resolver for a given chart, helm home and registry client.
    46  func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver {
    47  	return &Resolver{
    48  		chartpath:      chartpath,
    49  		cachepath:      cachepath,
    50  		registryClient: registryClient,
    51  	}
    52  }
    53  
    54  // Resolve resolves dependencies and returns a lock file with the resolution.
    55  func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
    56  
    57  	// Now we clone the dependencies, locking as we go.
    58  	locked := make([]*chart.Dependency, len(reqs))
    59  	missing := []string{}
    60  	for i, d := range reqs {
    61  		constraint, err := semver.NewConstraint(d.Version)
    62  		if err != nil {
    63  			return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
    64  		}
    65  
    66  		if d.Repository == "" {
    67  			// Local chart subfolder
    68  			if _, err := GetLocalPath(filepath.Join("charts", d.Name), r.chartpath); err != nil {
    69  				return nil, err
    70  			}
    71  
    72  			locked[i] = &chart.Dependency{
    73  				Name:       d.Name,
    74  				Repository: "",
    75  				Version:    d.Version,
    76  			}
    77  			continue
    78  		}
    79  		if strings.HasPrefix(d.Repository, "file://") {
    80  
    81  			chartpath, err := GetLocalPath(d.Repository, r.chartpath)
    82  			if err != nil {
    83  				return nil, err
    84  			}
    85  
    86  			ch, err := loader.LoadDir(chartpath)
    87  			if err != nil {
    88  				return nil, err
    89  			}
    90  
    91  			v, err := semver.NewVersion(ch.Metadata.Version)
    92  			if err != nil {
    93  				// Not a legit entry.
    94  				continue
    95  			}
    96  
    97  			if !constraint.Check(v) {
    98  				missing = append(missing, d.Name)
    99  				continue
   100  			}
   101  
   102  			locked[i] = &chart.Dependency{
   103  				Name:       d.Name,
   104  				Repository: d.Repository,
   105  				Version:    ch.Metadata.Version,
   106  			}
   107  			continue
   108  		}
   109  
   110  		repoName := repoNames[d.Name]
   111  		// if the repository was not defined, but the dependency defines a repository url, bypass the cache
   112  		if repoName == "" && d.Repository != "" {
   113  			locked[i] = &chart.Dependency{
   114  				Name:       d.Name,
   115  				Repository: d.Repository,
   116  				Version:    d.Version,
   117  			}
   118  			continue
   119  		}
   120  
   121  		var vs repo.ChartVersions
   122  		var version string
   123  		var ok bool
   124  		found := true
   125  		if !registry.IsOCI(d.Repository) {
   126  			repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
   127  			if err != nil {
   128  				return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
   129  			}
   130  
   131  			vs, ok = repoIndex.Entries[d.Name]
   132  			if !ok {
   133  				return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
   134  			}
   135  			found = false
   136  		} else {
   137  			version = d.Version
   138  
   139  			// Check to see if an explicit version has been provided
   140  			_, err := semver.NewVersion(version)
   141  
   142  			// Use an explicit version, otherwise search for tags
   143  			if err == nil {
   144  				vs = []*repo.ChartVersion{{
   145  					Metadata: &chart.Metadata{
   146  						Version: version,
   147  					},
   148  				}}
   149  
   150  			} else {
   151  				// Retrieve list of tags for repository
   152  				ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
   153  				tags, err := r.registryClient.Tags(ref)
   154  				if err != nil {
   155  					return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository)
   156  				}
   157  
   158  				vs = make(repo.ChartVersions, len(tags))
   159  				for ti, t := range tags {
   160  					// Mock chart version objects
   161  					version := &repo.ChartVersion{
   162  						Metadata: &chart.Metadata{
   163  							Version: t,
   164  						},
   165  					}
   166  					vs[ti] = version
   167  				}
   168  			}
   169  		}
   170  
   171  		locked[i] = &chart.Dependency{
   172  			Name:       d.Name,
   173  			Repository: d.Repository,
   174  			Version:    version,
   175  		}
   176  		// The version are already sorted and hence the first one to satisfy the constraint is used
   177  		for _, ver := range vs {
   178  			v, err := semver.NewVersion(ver.Version)
   179  			// OCI does not need URLs
   180  			if err != nil || (!registry.IsOCI(d.Repository) && len(ver.URLs) == 0) {
   181  				// Not a legit entry.
   182  				continue
   183  			}
   184  			if constraint.Check(v) {
   185  				found = true
   186  				locked[i].Version = v.Original()
   187  				break
   188  			}
   189  		}
   190  
   191  		if !found {
   192  			missing = append(missing, d.Name)
   193  		}
   194  	}
   195  	if len(missing) > 0 {
   196  		return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", "))
   197  	}
   198  
   199  	digest, err := HashReq(reqs, locked)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	return &chart.Lock{
   205  		Generated:    time.Now(),
   206  		Digest:       digest,
   207  		Dependencies: locked,
   208  	}, nil
   209  }
   210  
   211  // HashReq generates a hash of the dependencies.
   212  //
   213  // This should be used only to compare against another hash generated by this
   214  // function.
   215  func HashReq(req, lock []*chart.Dependency) (string, error) {
   216  	data, err := json.Marshal([2][]*chart.Dependency{req, lock})
   217  	if err != nil {
   218  		return "", err
   219  	}
   220  	s, err := provenance.Digest(bytes.NewBuffer(data))
   221  	return "sha256:" + s, err
   222  }
   223  
   224  // HashV2Req generates a hash of requirements generated in Helm v2.
   225  //
   226  // This should be used only to compare against another hash generated by the
   227  // Helm v2 hash function. It is to handle issue:
   228  // https://github.com/helm/helm/issues/7233
   229  func HashV2Req(req []*chart.Dependency) (string, error) {
   230  	dep := make(map[string][]*chart.Dependency)
   231  	dep["dependencies"] = req
   232  	data, err := json.Marshal(dep)
   233  	if err != nil {
   234  		return "", err
   235  	}
   236  	s, err := provenance.Digest(bytes.NewBuffer(data))
   237  	return "sha256:" + s, err
   238  }
   239  
   240  // GetLocalPath generates absolute local path when use
   241  // "file://" in repository of dependencies
   242  func GetLocalPath(repo, chartpath string) (string, error) {
   243  	var depPath string
   244  	var err error
   245  	p := strings.TrimPrefix(repo, "file://")
   246  
   247  	// root path is absolute
   248  	if strings.HasPrefix(p, "/") {
   249  		if depPath, err = filepath.Abs(p); err != nil {
   250  			return "", err
   251  		}
   252  	} else {
   253  		depPath = filepath.Join(chartpath, p)
   254  	}
   255  
   256  	if _, err = os.Stat(depPath); os.IsNotExist(err) {
   257  		return "", errors.Errorf("directory %s not found", depPath)
   258  	} else if err != nil {
   259  		return "", err
   260  	}
   261  
   262  	return depPath, nil
   263  }
   264  

View as plain text