github.com/koderover/helm@v2.17.0+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 "k8s.io/helm/pkg/repo"
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/url"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"github.com/ghodss/yaml"
    29  
    30  	"k8s.io/helm/pkg/chartutil"
    31  	"k8s.io/helm/pkg/getter"
    32  	"k8s.io/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, fmt.Errorf("invalid chart URL format: %s", cfg.URL)
    60  	}
    61  
    62  	getterConstructor, err := getters.ByScheme(u.Scheme)
    63  	if err != nil {
    64  		return nil, fmt.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, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err)
    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 fmt.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  	parsedURL, err := url.Parse(r.Config.URL)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
   120  	parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
   121  	indexURL := parsedURL.String()
   122  
   123  	r.setCredentials()
   124  	resp, err := r.Client.Get(indexURL)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	index, err := ioutil.ReadAll(resp)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	if _, err := loadIndex(index); err != nil {
   135  		return err
   136  	}
   137  
   138  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   139  	// path, which broke backward compatibility. This fixes it by prepending a
   140  	// global cache path to relative paths.
   141  	//
   142  	// It is changed on DownloadIndexFile because that was the method that
   143  	// originally carried the cache path.
   144  	cp := r.Config.Cache
   145  	if !filepath.IsAbs(cp) {
   146  		cp = filepath.Join(cachePath, cp)
   147  	}
   148  
   149  	return ioutil.WriteFile(cp, index, 0644)
   150  }
   151  
   152  // If HttpGetter is used, this method sets the configured repository credentials on the HttpGetter.
   153  func (r *ChartRepository) setCredentials() {
   154  	if t, ok := r.Client.(*getter.HttpGetter); ok {
   155  		t.SetCredentials(r.Config.Username, r.Config.Password)
   156  	}
   157  }
   158  
   159  // Index generates an index for the chart repository and writes an index.yaml file.
   160  func (r *ChartRepository) Index() error {
   161  	err := r.generateIndex()
   162  	if err != nil {
   163  		return err
   164  	}
   165  	return r.saveIndexFile()
   166  }
   167  
   168  func (r *ChartRepository) saveIndexFile() error {
   169  	index, err := yaml.Marshal(r.IndexFile)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   174  }
   175  
   176  func (r *ChartRepository) generateIndex() error {
   177  	for _, path := range r.ChartPaths {
   178  		ch, err := chartutil.Load(path)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		digest, err := provenance.DigestFile(path)
   184  		if err != nil {
   185  			return err
   186  		}
   187  
   188  		if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
   189  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   190  		}
   191  		// TODO: If a chart exists, but has a different Digest, should we error?
   192  	}
   193  	r.IndexFile.SortEntries()
   194  	return nil
   195  }
   196  
   197  // FindChartInRepoURL finds chart in chart repository pointed by repoURL
   198  // without adding repo to repositories
   199  func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   200  	return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters)
   201  }
   202  
   203  // FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL
   204  // without adding repo to repositories, like FindChartInRepoURL,
   205  // but it also receives credentials for the chart repository.
   206  func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   207  
   208  	// Download and write the index file to a temporary location
   209  	tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file")
   210  	if err != nil {
   211  		return "", fmt.Errorf("cannot write index file for repository requested")
   212  	}
   213  	defer os.Remove(tempIndexFile.Name())
   214  
   215  	c := Entry{
   216  		URL:      repoURL,
   217  		Username: username,
   218  		Password: password,
   219  		CertFile: certFile,
   220  		KeyFile:  keyFile,
   221  		CAFile:   caFile,
   222  	}
   223  	r, err := NewChartRepository(&c, getters)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  	if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil {
   228  		return "", fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", repoURL, err)
   229  	}
   230  
   231  	// Read the index file for the repository to get chart information and return chart URL
   232  	repoIndex, err := LoadIndexFile(tempIndexFile.Name())
   233  	if err != nil {
   234  		return "", err
   235  	}
   236  
   237  	errMsg := fmt.Sprintf("chart %q", chartName)
   238  	if chartVersion != "" {
   239  		errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
   240  	}
   241  	cv, err := repoIndex.Get(chartName, chartVersion)
   242  	if err != nil {
   243  		return "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL)
   244  	}
   245  
   246  	if len(cv.URLs) == 0 {
   247  		return "", fmt.Errorf("%s has no downloadable URLs", errMsg)
   248  	}
   249  
   250  	chartURL := cv.URLs[0]
   251  
   252  	absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
   253  	if err != nil {
   254  		return "", fmt.Errorf("failed to make chart URL absolute: %v", err)
   255  	}
   256  
   257  	return absoluteChartURL, nil
   258  }
   259  
   260  // ResolveReferenceURL resolves refURL relative to baseURL.
   261  // If refURL is absolute, it simply returns refURL.
   262  func ResolveReferenceURL(baseURL, refURL string) (string, error) {
   263  	parsedBaseURL, err := url.Parse(baseURL)
   264  	if err != nil {
   265  		return "", fmt.Errorf("failed to parse %s as URL: %v", baseURL, err)
   266  	}
   267  
   268  	parsedRefURL, err := url.Parse(refURL)
   269  	if err != nil {
   270  		return "", fmt.Errorf("failed to parse %s as URL: %v", refURL, err)
   271  	}
   272  
   273  	// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
   274  	parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
   275  	resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL)
   276  	// if the base URL contains query string parameters,
   277  	// propagate them to the child URL but only if the
   278  	// refURL is relative to baseURL
   279  	if (resolvedURL.Hostname() == parsedBaseURL.Hostname()) && (resolvedURL.Port() == parsedBaseURL.Port()) {
   280  		resolvedURL.RawQuery = parsedBaseURL.RawQuery
   281  	}
   282  
   283  	return resolvedURL.String(), nil
   284  }