github.com/pensu/helm@v2.6.1+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", u.Scheme)
    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  
   114  	indexURL = strings.TrimSuffix(r.Config.URL, "/") + "/index.yaml"
   115  	resp, err := r.Client.Get(indexURL)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	index, err := ioutil.ReadAll(resp)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	if _, err := loadIndex(index); err != nil {
   126  		return err
   127  	}
   128  
   129  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   130  	// path, which broke backward compatibility. This fixes it by prepending a
   131  	// global cache path to relative paths.
   132  	//
   133  	// It is changed on DownloadIndexFile because that was the method that
   134  	// originally carried the cache path.
   135  	cp := r.Config.Cache
   136  	if !filepath.IsAbs(cp) {
   137  		cp = filepath.Join(cachePath, cp)
   138  	}
   139  
   140  	return ioutil.WriteFile(cp, index, 0644)
   141  }
   142  
   143  // Index generates an index for the chart repository and writes an index.yaml file.
   144  func (r *ChartRepository) Index() error {
   145  	err := r.generateIndex()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	return r.saveIndexFile()
   150  }
   151  
   152  func (r *ChartRepository) saveIndexFile() error {
   153  	index, err := yaml.Marshal(r.IndexFile)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   158  }
   159  
   160  func (r *ChartRepository) generateIndex() error {
   161  	for _, path := range r.ChartPaths {
   162  		ch, err := chartutil.Load(path)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		digest, err := provenance.DigestFile(path)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
   173  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   174  		}
   175  		// TODO: If a chart exists, but has a different Digest, should we error?
   176  	}
   177  	r.IndexFile.SortEntries()
   178  	return nil
   179  }
   180  
   181  // FindChartInRepoURL finds chart in chart repository pointed by repoURL
   182  // without adding repo to repostiories
   183  func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
   184  
   185  	// Download and write the index file to a temporary location
   186  	tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file")
   187  	if err != nil {
   188  		return "", fmt.Errorf("cannot write index file for repository requested")
   189  	}
   190  	defer os.Remove(tempIndexFile.Name())
   191  
   192  	c := Entry{
   193  		URL:      repoURL,
   194  		CertFile: certFile,
   195  		KeyFile:  keyFile,
   196  		CAFile:   caFile,
   197  	}
   198  	r, err := NewChartRepository(&c, getters)
   199  	if err != nil {
   200  		return "", err
   201  	}
   202  	if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil {
   203  		return "", fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", repoURL, err)
   204  	}
   205  
   206  	// Read the index file for the repository to get chart information and return chart URL
   207  	repoIndex, err := LoadIndexFile(tempIndexFile.Name())
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  
   212  	errMsg := fmt.Sprintf("chart %q", chartName)
   213  	if chartVersion != "" {
   214  		errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
   215  	}
   216  	cv, err := repoIndex.Get(chartName, chartVersion)
   217  	if err != nil {
   218  		return "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL)
   219  	}
   220  
   221  	if len(cv.URLs) == 0 {
   222  		return "", fmt.Errorf("%s has no downloadable URLs", errMsg)
   223  	}
   224  
   225  	return cv.URLs[0], nil
   226  }