github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+incompatible/pkg/repo/chartrepo.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package repo // import "helm.sh/helm/pkg/repo"
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/url"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/pkg/errors"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	"helm.sh/helm/pkg/chart/loader"
    31  	"helm.sh/helm/pkg/getter"
    32  	"helm.sh/helm/pkg/provenance"
    33  )
    34  
    35  // Entry represents a collection of parameters for chart repository
    36  type Entry struct {
    37  	Name     string `json:"name"`
    38  	Cache    string `json:"cache"`
    39  	URL      string `json:"url"`
    40  	Username string `json:"username"`
    41  	Password string `json:"password"`
    42  	CertFile string `json:"certFile"`
    43  	KeyFile  string `json:"keyFile"`
    44  	CAFile   string `json:"caFile"`
    45  }
    46  
    47  // ChartRepository represents a chart repository
    48  type ChartRepository struct {
    49  	Config     *Entry
    50  	ChartPaths []string
    51  	IndexFile  *IndexFile
    52  	Client     getter.Getter
    53  }
    54  
    55  // NewChartRepository constructs ChartRepository
    56  func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) {
    57  	u, err := url.Parse(cfg.URL)
    58  	if err != nil {
    59  		return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL)
    60  	}
    61  
    62  	getterConstructor, err := getters.ByScheme(u.Scheme)
    63  	if err != nil {
    64  		return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme)
    65  	}
    66  	client, err := getterConstructor(
    67  		getter.WithURL(cfg.URL),
    68  		getter.WithTLSClientConfig(cfg.CertFile, cfg.KeyFile, cfg.CAFile),
    69  	)
    70  	if err != nil {
    71  		return nil, errors.Wrapf(err, "could not construct protocol handler for: %s", u.Scheme)
    72  	}
    73  
    74  	return &ChartRepository{
    75  		Config:    cfg,
    76  		IndexFile: NewIndexFile(),
    77  		Client:    client,
    78  	}, nil
    79  }
    80  
    81  // Load loads a directory of charts as if it were a repository.
    82  //
    83  // It requires the presence of an index.yaml file in the directory.
    84  func (r *ChartRepository) Load() error {
    85  	dirInfo, err := os.Stat(r.Config.Name)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	if !dirInfo.IsDir() {
    90  		return errors.Errorf("%q is not a directory", r.Config.Name)
    91  	}
    92  
    93  	// FIXME: Why are we recursively walking directories?
    94  	// FIXME: Why are we not reading the repositories.yaml to figure out
    95  	// what repos to use?
    96  	filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
    97  		if !f.IsDir() {
    98  			if strings.Contains(f.Name(), "-index.yaml") {
    99  				i, err := LoadIndexFile(path)
   100  				if err != nil {
   101  					return nil
   102  				}
   103  				r.IndexFile = i
   104  			} else if strings.HasSuffix(f.Name(), ".tgz") {
   105  				r.ChartPaths = append(r.ChartPaths, path)
   106  			}
   107  		}
   108  		return nil
   109  	})
   110  	return nil
   111  }
   112  
   113  // DownloadIndexFile fetches the index from a repository.
   114  //
   115  // cachePath is prepended to any index that does not have an absolute path. This
   116  // is for pre-2.2.0 repo files.
   117  func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
   118  	var indexURL string
   119  	parsedURL, err := url.Parse(r.Config.URL)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml"
   124  
   125  	indexURL = parsedURL.String()
   126  	// TODO add user-agent
   127  	g, err := getter.NewHTTPGetter(
   128  		getter.WithURL(indexURL),
   129  		getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile),
   130  		getter.WithBasicAuth(r.Config.Username, r.Config.Password),
   131  	)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	resp, err := g.Get(indexURL)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	index, err := ioutil.ReadAll(resp)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	if _, err := loadIndex(index); err != nil {
   146  		return err
   147  	}
   148  
   149  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   150  	// path, which broke backward compatibility. This fixes it by prepending a
   151  	// global cache path to relative paths.
   152  	//
   153  	// It is changed on DownloadIndexFile because that was the method that
   154  	// originally carried the cache path.
   155  	cp := r.Config.Cache
   156  	if !filepath.IsAbs(cp) {
   157  		cp = filepath.Join(cachePath, cp)
   158  	}
   159  
   160  	return ioutil.WriteFile(cp, index, 0644)
   161  }
   162  
   163  // Index generates an index for the chart repository and writes an index.yaml file.
   164  func (r *ChartRepository) Index() error {
   165  	err := r.generateIndex()
   166  	if err != nil {
   167  		return err
   168  	}
   169  	return r.saveIndexFile()
   170  }
   171  
   172  func (r *ChartRepository) saveIndexFile() error {
   173  	index, err := yaml.Marshal(r.IndexFile)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   178  }
   179  
   180  func (r *ChartRepository) generateIndex() error {
   181  	for _, path := range r.ChartPaths {
   182  		ch, err := loader.Load(path)
   183  		if err != nil {
   184  			return err
   185  		}
   186  
   187  		digest, err := provenance.DigestFile(path)
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) {
   193  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   194  		}
   195  		// TODO: If a chart exists, but has a different Digest, should we error?
   196  	}
   197  	r.IndexFile.SortEntries()
   198  	return nil
   199  }
   200  
   201  // FindChartInRepoURL finds chart in chart repository pointed by repoURL
   202  // without adding repo to repositories
   203  func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   204  	return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters)
   205  }
   206  
   207  // FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL
   208  // without adding repo to repositories, like FindChartInRepoURL,
   209  // but it also receives credentials for the chart repository.
   210  func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   211  
   212  	// Download and write the index file to a temporary location
   213  	tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file")
   214  	if err != nil {
   215  		return "", errors.Errorf("cannot write index file for repository requested")
   216  	}
   217  	defer os.Remove(tempIndexFile.Name())
   218  
   219  	c := Entry{
   220  		URL:      repoURL,
   221  		Username: username,
   222  		Password: password,
   223  		CertFile: certFile,
   224  		KeyFile:  keyFile,
   225  		CAFile:   caFile,
   226  	}
   227  	r, err := NewChartRepository(&c, getters)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  	if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil {
   232  		return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
   233  	}
   234  
   235  	// Read the index file for the repository to get chart information and return chart URL
   236  	repoIndex, err := LoadIndexFile(tempIndexFile.Name())
   237  	if err != nil {
   238  		return "", err
   239  	}
   240  
   241  	errMsg := fmt.Sprintf("chart %q", chartName)
   242  	if chartVersion != "" {
   243  		errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
   244  	}
   245  	cv, err := repoIndex.Get(chartName, chartVersion)
   246  	if err != nil {
   247  		return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
   248  	}
   249  
   250  	if len(cv.URLs) == 0 {
   251  		return "", errors.Errorf("%s has no downloadable URLs", errMsg)
   252  	}
   253  
   254  	chartURL := cv.URLs[0]
   255  
   256  	absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
   257  	if err != nil {
   258  		return "", errors.Wrap(err, "failed to make chart URL absolute")
   259  	}
   260  
   261  	return absoluteChartURL, nil
   262  }
   263  
   264  // ResolveReferenceURL resolves refURL relative to baseURL.
   265  // If refURL is absolute, it simply returns refURL.
   266  func ResolveReferenceURL(baseURL, refURL string) (string, error) {
   267  	parsedBaseURL, err := url.Parse(baseURL)
   268  	if err != nil {
   269  		return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
   270  	}
   271  
   272  	parsedRefURL, err := url.Parse(refURL)
   273  	if err != nil {
   274  		return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
   275  	}
   276  
   277  	return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
   278  }