github.com/valdemarpavesi/helm@v2.9.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 Username string `json:"username"` 40 Password string `json:"password"` 41 CertFile string `json:"certFile"` 42 KeyFile string `json:"keyFile"` 43 CAFile string `json:"caFile"` 44 } 45 46 // ChartRepository represents a chart repository 47 type ChartRepository struct { 48 Config *Entry 49 ChartPaths []string 50 IndexFile *IndexFile 51 Client getter.Getter 52 } 53 54 // NewChartRepository constructs ChartRepository 55 func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) { 56 u, err := url.Parse(cfg.URL) 57 if err != nil { 58 return nil, fmt.Errorf("invalid chart URL format: %s", cfg.URL) 59 } 60 61 getterConstructor, err := getters.ByScheme(u.Scheme) 62 if err != nil { 63 return nil, fmt.Errorf("Could not find protocol handler for: %s", u.Scheme) 64 } 65 client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile) 66 if err != nil { 67 return nil, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err) 68 } 69 70 return &ChartRepository{ 71 Config: cfg, 72 IndexFile: NewIndexFile(), 73 Client: client, 74 }, nil 75 } 76 77 // Load loads a directory of charts as if it were a repository. 78 // 79 // It requires the presence of an index.yaml file in the directory. 80 func (r *ChartRepository) Load() error { 81 dirInfo, err := os.Stat(r.Config.Name) 82 if err != nil { 83 return err 84 } 85 if !dirInfo.IsDir() { 86 return fmt.Errorf("%q is not a directory", r.Config.Name) 87 } 88 89 // FIXME: Why are we recursively walking directories? 90 // FIXME: Why are we not reading the repositories.yaml to figure out 91 // what repos to use? 92 filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error { 93 if !f.IsDir() { 94 if strings.Contains(f.Name(), "-index.yaml") { 95 i, err := LoadIndexFile(path) 96 if err != nil { 97 return nil 98 } 99 r.IndexFile = i 100 } else if strings.HasSuffix(f.Name(), ".tgz") { 101 r.ChartPaths = append(r.ChartPaths, path) 102 } 103 } 104 return nil 105 }) 106 return nil 107 } 108 109 // DownloadIndexFile fetches the index from a repository. 110 // 111 // cachePath is prepended to any index that does not have an absolute path. This 112 // is for pre-2.2.0 repo files. 113 func (r *ChartRepository) DownloadIndexFile(cachePath string) error { 114 var indexURL string 115 parsedURL, err := url.Parse(r.Config.URL) 116 if err != nil { 117 return err 118 } 119 parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" 120 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 return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil 274 }