github.com/umeshredd/helm@v3.0.0-alpha.1+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/ghodss/yaml"
    28  	"github.com/pkg/errors"
    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(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile)
    67  	if err != nil {
    68  		return nil, errors.Wrapf(err, "could not construct protocol handler for: %s", u.Scheme)
    69  	}
    70  
    71  	return &ChartRepository{
    72  		Config:    cfg,
    73  		IndexFile: NewIndexFile(),
    74  		Client:    client,
    75  	}, nil
    76  }
    77  
    78  // Load loads a directory of charts as if it were a repository.
    79  //
    80  // It requires the presence of an index.yaml file in the directory.
    81  func (r *ChartRepository) Load() error {
    82  	dirInfo, err := os.Stat(r.Config.Name)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	if !dirInfo.IsDir() {
    87  		return errors.Errorf("%q is not a directory", r.Config.Name)
    88  	}
    89  
    90  	// FIXME: Why are we recursively walking directories?
    91  	// FIXME: Why are we not reading the repositories.yaml to figure out
    92  	// what repos to use?
    93  	filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
    94  		if !f.IsDir() {
    95  			if strings.Contains(f.Name(), "-index.yaml") {
    96  				i, err := LoadIndexFile(path)
    97  				if err != nil {
    98  					return nil
    99  				}
   100  				r.IndexFile = i
   101  			} else if strings.HasSuffix(f.Name(), ".tgz") {
   102  				r.ChartPaths = append(r.ChartPaths, path)
   103  			}
   104  		}
   105  		return nil
   106  	})
   107  	return nil
   108  }
   109  
   110  // DownloadIndexFile fetches the index from a repository.
   111  //
   112  // cachePath is prepended to any index that does not have an absolute path. This
   113  // is for pre-2.2.0 repo files.
   114  func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
   115  	var indexURL string
   116  	parsedURL, err := url.Parse(r.Config.URL)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml"
   121  
   122  	indexURL = parsedURL.String()
   123  	// TODO add user-agent
   124  	g, err := getter.NewHTTPGetter(indexURL, r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	g.SetCredentials(r.Config.Username, r.Config.Password)
   129  	resp, err := g.Get(indexURL)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	index, err := ioutil.ReadAll(resp)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if _, err := loadIndex(index); err != nil {
   140  		return err
   141  	}
   142  
   143  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   144  	// path, which broke backward compatibility. This fixes it by prepending a
   145  	// global cache path to relative paths.
   146  	//
   147  	// It is changed on DownloadIndexFile because that was the method that
   148  	// originally carried the cache path.
   149  	cp := r.Config.Cache
   150  	if !filepath.IsAbs(cp) {
   151  		cp = filepath.Join(cachePath, cp)
   152  	}
   153  
   154  	return ioutil.WriteFile(cp, index, 0644)
   155  }
   156  
   157  // Index generates an index for the chart repository and writes an index.yaml file.
   158  func (r *ChartRepository) Index() error {
   159  	err := r.generateIndex()
   160  	if err != nil {
   161  		return err
   162  	}
   163  	return r.saveIndexFile()
   164  }
   165  
   166  func (r *ChartRepository) saveIndexFile() error {
   167  	index, err := yaml.Marshal(r.IndexFile)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   172  }
   173  
   174  func (r *ChartRepository) generateIndex() error {
   175  	for _, path := range r.ChartPaths {
   176  		ch, err := loader.Load(path)
   177  		if err != nil {
   178  			return err
   179  		}
   180  
   181  		digest, err := provenance.DigestFile(path)
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) {
   187  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   188  		}
   189  		// TODO: If a chart exists, but has a different Digest, should we error?
   190  	}
   191  	r.IndexFile.SortEntries()
   192  	return nil
   193  }
   194  
   195  // FindChartInRepoURL finds chart in chart repository pointed by repoURL
   196  // without adding repo to repositories
   197  func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   198  	return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters)
   199  }
   200  
   201  // FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL
   202  // without adding repo to repositories, like FindChartInRepoURL,
   203  // but it also receives credentials for the chart repository.
   204  func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   205  
   206  	// Download and write the index file to a temporary location
   207  	tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file")
   208  	if err != nil {
   209  		return "", errors.Errorf("cannot write index file for repository requested")
   210  	}
   211  	defer os.Remove(tempIndexFile.Name())
   212  
   213  	c := Entry{
   214  		URL:      repoURL,
   215  		Username: username,
   216  		Password: password,
   217  		CertFile: certFile,
   218  		KeyFile:  keyFile,
   219  		CAFile:   caFile,
   220  	}
   221  	r, err := NewChartRepository(&c, getters)
   222  	if err != nil {
   223  		return "", err
   224  	}
   225  	if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil {
   226  		return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
   227  	}
   228  
   229  	// Read the index file for the repository to get chart information and return chart URL
   230  	repoIndex, err := LoadIndexFile(tempIndexFile.Name())
   231  	if err != nil {
   232  		return "", err
   233  	}
   234  
   235  	errMsg := fmt.Sprintf("chart %q", chartName)
   236  	if chartVersion != "" {
   237  		errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
   238  	}
   239  	cv, err := repoIndex.Get(chartName, chartVersion)
   240  	if err != nil {
   241  		return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
   242  	}
   243  
   244  	if len(cv.URLs) == 0 {
   245  		return "", errors.Errorf("%s has no downloadable URLs", errMsg)
   246  	}
   247  
   248  	chartURL := cv.URLs[0]
   249  
   250  	absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
   251  	if err != nil {
   252  		return "", errors.Wrap(err, "failed to make chart URL absolute")
   253  	}
   254  
   255  	return absoluteChartURL, nil
   256  }
   257  
   258  // ResolveReferenceURL resolves refURL relative to baseURL.
   259  // If refURL is absolute, it simply returns refURL.
   260  func ResolveReferenceURL(baseURL, refURL string) (string, error) {
   261  	parsedBaseURL, err := url.Parse(baseURL)
   262  	if err != nil {
   263  		return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
   264  	}
   265  
   266  	parsedRefURL, err := url.Parse(refURL)
   267  	if err != nil {
   268  		return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
   269  	}
   270  
   271  	return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
   272  }