github.com/y-taka-23/helm@v2.8.0+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 }