github.com/cloudposse/helm@v2.2.3+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/http"
    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/provenance"
    31  	"k8s.io/helm/pkg/tlsutil"
    32  	"k8s.io/helm/pkg/urlutil"
    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  	CertFile string `json:"certFile"`
    41  	KeyFile  string `json:"keyFile"`
    42  	CAFile   string `json:"caFile"`
    43  }
    44  
    45  // ChartRepository represents a chart repository
    46  type ChartRepository struct {
    47  	Config     *Entry
    48  	ChartPaths []string
    49  	IndexFile  *IndexFile
    50  	Client     *http.Client
    51  }
    52  
    53  // Getter is an interface to support GET to the specified URL.
    54  type Getter interface {
    55  	Get(url string) (*http.Response, error)
    56  }
    57  
    58  // NewChartRepository constructs ChartRepository
    59  func NewChartRepository(cfg *Entry) (*ChartRepository, error) {
    60  	var client *http.Client
    61  	if cfg.CertFile != "" && cfg.KeyFile != "" && cfg.CAFile != "" {
    62  		tlsConf, err := tlsutil.NewClientTLS(cfg.CertFile, cfg.KeyFile, cfg.CAFile)
    63  		if err != nil {
    64  			return nil, fmt.Errorf("can't create TLS config for client: %s", err.Error())
    65  		}
    66  		tlsConf.BuildNameToCertificate()
    67  
    68  		sni, err := urlutil.ExtractHostname(cfg.URL)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		tlsConf.ServerName = sni
    73  
    74  		client = &http.Client{
    75  			Transport: &http.Transport{
    76  				TLSClientConfig: tlsConf,
    77  			},
    78  		}
    79  	} else {
    80  		client = http.DefaultClient
    81  	}
    82  
    83  	return &ChartRepository{
    84  		Config:    cfg,
    85  		IndexFile: NewIndexFile(),
    86  		Client:    client,
    87  	}, nil
    88  }
    89  
    90  // Get issues a GET using configured client to the specified URL.
    91  func (r *ChartRepository) Get(url string) (*http.Response, error) {
    92  	resp, err := r.Client.Get(url)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	return resp, nil
    97  }
    98  
    99  // Load loads a directory of charts as if it were a repository.
   100  //
   101  // It requires the presence of an index.yaml file in the directory.
   102  func (r *ChartRepository) Load() error {
   103  	dirInfo, err := os.Stat(r.Config.Name)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if !dirInfo.IsDir() {
   108  		return fmt.Errorf("%q is not a directory", r.Config.Name)
   109  	}
   110  
   111  	// FIXME: Why are we recursively walking directories?
   112  	// FIXME: Why are we not reading the repositories.yaml to figure out
   113  	// what repos to use?
   114  	filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
   115  		if !f.IsDir() {
   116  			if strings.Contains(f.Name(), "-index.yaml") {
   117  				i, err := LoadIndexFile(path)
   118  				if err != nil {
   119  					return nil
   120  				}
   121  				r.IndexFile = i
   122  			} else if strings.HasSuffix(f.Name(), ".tgz") {
   123  				r.ChartPaths = append(r.ChartPaths, path)
   124  			}
   125  		}
   126  		return nil
   127  	})
   128  	return nil
   129  }
   130  
   131  // DownloadIndexFile fetches the index from a repository.
   132  //
   133  // cachePath is prepended to any index that does not have an absolute path. This
   134  // is for pre-2.2.0 repo files.
   135  func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
   136  	var indexURL string
   137  
   138  	indexURL = strings.TrimSuffix(r.Config.URL, "/") + "/index.yaml"
   139  	resp, err := r.Get(indexURL)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	defer resp.Body.Close()
   144  
   145  	index, err := ioutil.ReadAll(resp.Body)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	if _, err := loadIndex(index); err != nil {
   151  		return err
   152  	}
   153  
   154  	// In Helm 2.2.0 the config.cache was accidentally switched to an absolute
   155  	// path, which broke backward compatibility. This fixes it by prepending a
   156  	// global cache path to relative paths.
   157  	//
   158  	// It is changed on DownloadIndexFile because that was the method that
   159  	// originally carried the cache path.
   160  	cp := r.Config.Cache
   161  	if !filepath.IsAbs(cp) {
   162  		cp = filepath.Join(cachePath, cp)
   163  	}
   164  	println("Writing to", cp)
   165  
   166  	return ioutil.WriteFile(cp, index, 0644)
   167  }
   168  
   169  // Index generates an index for the chart repository and writes an index.yaml file.
   170  func (r *ChartRepository) Index() error {
   171  	err := r.generateIndex()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	return r.saveIndexFile()
   176  }
   177  
   178  func (r *ChartRepository) saveIndexFile() error {
   179  	index, err := yaml.Marshal(r.IndexFile)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
   184  }
   185  
   186  func (r *ChartRepository) generateIndex() error {
   187  	for _, path := range r.ChartPaths {
   188  		ch, err := chartutil.Load(path)
   189  		if err != nil {
   190  			return err
   191  		}
   192  
   193  		digest, err := provenance.DigestFile(path)
   194  		if err != nil {
   195  			return err
   196  		}
   197  
   198  		if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
   199  			r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
   200  		}
   201  		// TODO: If a chart exists, but has a different Digest, should we error?
   202  	}
   203  	r.IndexFile.SortEntries()
   204  	return nil
   205  }