github.com/darkowlzz/helm@v2.5.1-0.20171213183701-6707fe0468d4+incompatible/pkg/repo/chartrepo.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     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/filepath"
    25  	"strings"
    26  
    27  	"github.com/ghodss/yaml"
    28  
    29  	"k8s.io/helm/pkg/chartutil"
    30  	"k8s.io/helm/pkg/getter"
    31  	"k8s.io/helm/pkg/provenance"
    32  )
    33  
    34  // Entry represents a collection of parameters for chart repository
    35  type Entry struct {
    36  	Name     string `json:"name"`
    37  	Cache    string `json:"cache"`
    38  	URL      string `json:"url"`
    39  	CertFile string `json:"certFile"`
    40  	KeyFile  string `json:"keyFile"`
    41  	CAFile   string `json:"caFile"`
    42  }
    43  
    44  // ChartRepository represents a chart repository
    45  type ChartRepository struct {
    46  	Config     *Entry
    47  	ChartPaths []string
    48  	IndexFile  *IndexFile
    49  	Client     getter.Getter
    50  }
    51  
    52  // NewChartRepository constructs ChartRepository
    53  func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) {
    54  	u, err := url.Parse(cfg.URL)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("invalid chart URL format: %s", cfg.URL)
    57  	}
    58  
    59  	getterConstructor, err := getters.ByScheme(u.Scheme)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("Could not find protocol handler for: %s", u.Scheme)
    62  	}
    63  	client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err)
    66  	}
    67  
    68  	return &ChartRepository{
    69  		Config:    cfg,
    70  		IndexFile: NewIndexFile(),
    71  		Client:    client,
    72  	}, nil
    73  }
    74  
    75  // Load loads a directory of charts as if it were a repository.
    76  //
    77  // It requires the presence of an index.yaml file in the directory.
    78  func (r *ChartRepository) Load() error {
    79  	dirInfo, err := os.Stat(r.Config.Name)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	if !dirInfo.IsDir() {
    84  		return fmt.Errorf("%q is not a directory", r.Config.Name)
    85  	}
    86  
    87  	// FIXME: Why are we recursively walking directories?
    88  	// FIXME: Why are we not reading the repositories.yaml to figure out
    89  	// what repos to use?
    90  	filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
    91  		if !f.IsDir() {
    92  			if strings.Contains(f.Name(), "-index.yaml") {
    93  				i, err := LoadIndexFile(path)
    94  				if err != nil {
    95  					return nil
    96  				}
    97  				r.IndexFile = i
    98  			} else if strings.HasSuffix(f.Name(), ".tgz") {
    99  				r.ChartPaths = append(r.ChartPaths, path)
   100  			}
   101  		}
   102  		return nil
   103  	})
   104  	return nil
   105  }
   106  
   107  // DownloadIndexFile fetches the index from a repository.
   108  //
   109  // cachePath is prepended to any index that does not have an absolute path. This
   110  // is for pre-2.2.0 repo files.
   111  func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
   112  	var indexURL string
   113  	parsedURL, err := url.Parse(r.Config.URL)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml"
   118  
   119  	indexURL = parsedURL.String()
   120  	resp, err := r.Client.Get(indexURL)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	index, err := ioutil.ReadAll(resp)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	if _, err := loadIndex(index); err != nil {
   131  		return err
   132  	}
   133  
   134  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   135  	// path, which broke backward compatibility. This fixes it by prepending a
   136  	// global cache path to relative paths.
   137  	//
   138  	// It is changed on DownloadIndexFile because that was the method that
   139  	// originally carried the cache path.
   140  	cp := r.Config.Cache
   141  	if !filepath.IsAbs(cp) {
   142  		cp = filepath.Join(cachePath, cp)
   143  	}
   144  
   145  	return ioutil.WriteFile(cp, index, 0644)
   146  }
   147  
   148  // Index generates an index for the chart repository and writes an index.yaml file.
   149  func (r *ChartRepository) Index() error {
   150  	err := r.generateIndex()
   151  	if err != nil {
   152  		return err
   153  	}
   154  	return r.saveIndexFile()
   155  }
   156  
   157  func (r *ChartRepository) saveIndexFile() error {
   158  	index, err := yaml.Marshal(r.IndexFile)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   163  }
   164  
   165  func (r *ChartRepository) generateIndex() error {
   166  	for _, path := range r.ChartPaths {
   167  		ch, err := chartutil.Load(path)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		digest, err := provenance.DigestFile(path)
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
   178  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   179  		}
   180  		// TODO: If a chart exists, but has a different Digest, should we error?
   181  	}
   182  	r.IndexFile.SortEntries()
   183  	return nil
   184  }
   185  
   186  // FindChartInRepoURL finds chart in chart repository pointed by repoURL
   187  // without adding repo to repositories
   188  func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   189  
   190  	// Download and write the index file to a temporary location
   191  	tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file")
   192  	if err != nil {
   193  		return "", fmt.Errorf("cannot write index file for repository requested")
   194  	}
   195  	defer os.Remove(tempIndexFile.Name())
   196  
   197  	c := Entry{
   198  		URL:      repoURL,
   199  		CertFile: certFile,
   200  		KeyFile:  keyFile,
   201  		CAFile:   caFile,
   202  	}
   203  	r, err := NewChartRepository(&c, getters)
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  	if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil {
   208  		return "", fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", repoURL, err)
   209  	}
   210  
   211  	// Read the index file for the repository to get chart information and return chart URL
   212  	repoIndex, err := LoadIndexFile(tempIndexFile.Name())
   213  	if err != nil {
   214  		return "", err
   215  	}
   216  
   217  	errMsg := fmt.Sprintf("chart %q", chartName)
   218  	if chartVersion != "" {
   219  		errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
   220  	}
   221  	cv, err := repoIndex.Get(chartName, chartVersion)
   222  	if err != nil {
   223  		return "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL)
   224  	}
   225  
   226  	if len(cv.URLs) == 0 {
   227  		return "", fmt.Errorf("%s has no downloadable URLs", errMsg)
   228  	}
   229  
   230  	chartURL := cv.URLs[0]
   231  
   232  	absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
   233  	if err != nil {
   234  		return "", fmt.Errorf("failed to make chart URL absolute: %v", err)
   235  	}
   236  
   237  	return absoluteChartURL, nil
   238  }
   239  
   240  // ResolveReferenceURL resolves refURL relative to baseURL.
   241  // If refURL is absolute, it simply returns refURL.
   242  func ResolveReferenceURL(baseURL, refURL string) (string, error) {
   243  	parsedBaseURL, err := url.Parse(baseURL)
   244  	if err != nil {
   245  		return "", fmt.Errorf("failed to parse %s as URL: %v", baseURL, err)
   246  	}
   247  
   248  	parsedRefURL, err := url.Parse(refURL)
   249  	if err != nil {
   250  		return "", fmt.Errorf("failed to parse %s as URL: %v", refURL, err)
   251  	}
   252  
   253  	return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
   254  }