github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+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 165 return ioutil.WriteFile(cp, index, 0644) 166 } 167 168 // Index generates an index for the chart repository and writes an index.yaml file. 169 func (r *ChartRepository) Index() error { 170 err := r.generateIndex() 171 if err != nil { 172 return err 173 } 174 return r.saveIndexFile() 175 } 176 177 func (r *ChartRepository) saveIndexFile() error { 178 index, err := yaml.Marshal(r.IndexFile) 179 if err != nil { 180 return err 181 } 182 return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644) 183 } 184 185 func (r *ChartRepository) generateIndex() error { 186 for _, path := range r.ChartPaths { 187 ch, err := chartutil.Load(path) 188 if err != nil { 189 return err 190 } 191 192 digest, err := provenance.DigestFile(path) 193 if err != nil { 194 return err 195 } 196 197 if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) { 198 r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest) 199 } 200 // TODO: If a chart exists, but has a different Digest, should we error? 201 } 202 r.IndexFile.SortEntries() 203 return nil 204 }