github.com/umeshredd/helm@v3.0.0-alpha.1+incompatible/pkg/repo/chartrepo.go (about) 1 /* 2 Copyright The Helm Authors. 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 "helm.sh/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 "github.com/pkg/errors" 29 30 "helm.sh/helm/pkg/chart/loader" 31 "helm.sh/helm/pkg/getter" 32 "helm.sh/helm/pkg/provenance" 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 Username string `json:"username"` 41 Password string `json:"password"` 42 CertFile string `json:"certFile"` 43 KeyFile string `json:"keyFile"` 44 CAFile string `json:"caFile"` 45 } 46 47 // ChartRepository represents a chart repository 48 type ChartRepository struct { 49 Config *Entry 50 ChartPaths []string 51 IndexFile *IndexFile 52 Client getter.Getter 53 } 54 55 // NewChartRepository constructs ChartRepository 56 func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) { 57 u, err := url.Parse(cfg.URL) 58 if err != nil { 59 return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL) 60 } 61 62 getterConstructor, err := getters.ByScheme(u.Scheme) 63 if err != nil { 64 return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme) 65 } 66 client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile) 67 if err != nil { 68 return nil, errors.Wrapf(err, "could not construct protocol handler for: %s", u.Scheme) 69 } 70 71 return &ChartRepository{ 72 Config: cfg, 73 IndexFile: NewIndexFile(), 74 Client: client, 75 }, nil 76 } 77 78 // Load loads a directory of charts as if it were a repository. 79 // 80 // It requires the presence of an index.yaml file in the directory. 81 func (r *ChartRepository) Load() error { 82 dirInfo, err := os.Stat(r.Config.Name) 83 if err != nil { 84 return err 85 } 86 if !dirInfo.IsDir() { 87 return errors.Errorf("%q is not a directory", r.Config.Name) 88 } 89 90 // FIXME: Why are we recursively walking directories? 91 // FIXME: Why are we not reading the repositories.yaml to figure out 92 // what repos to use? 93 filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error { 94 if !f.IsDir() { 95 if strings.Contains(f.Name(), "-index.yaml") { 96 i, err := LoadIndexFile(path) 97 if err != nil { 98 return nil 99 } 100 r.IndexFile = i 101 } else if strings.HasSuffix(f.Name(), ".tgz") { 102 r.ChartPaths = append(r.ChartPaths, path) 103 } 104 } 105 return nil 106 }) 107 return nil 108 } 109 110 // DownloadIndexFile fetches the index from a repository. 111 // 112 // cachePath is prepended to any index that does not have an absolute path. This 113 // is for pre-2.2.0 repo files. 114 func (r *ChartRepository) DownloadIndexFile(cachePath string) error { 115 var indexURL string 116 parsedURL, err := url.Parse(r.Config.URL) 117 if err != nil { 118 return err 119 } 120 parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" 121 122 indexURL = parsedURL.String() 123 // TODO add user-agent 124 g, err := getter.NewHTTPGetter(indexURL, r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile) 125 if err != nil { 126 return err 127 } 128 g.SetCredentials(r.Config.Username, r.Config.Password) 129 resp, err := g.Get(indexURL) 130 if err != nil { 131 return err 132 } 133 134 index, err := ioutil.ReadAll(resp) 135 if err != nil { 136 return err 137 } 138 139 if _, err := loadIndex(index); err != nil { 140 return err 141 } 142 143 // In Helm 2.2.0 the config.cache was accidentally switched to an absolute 144 // path, which broke backward compatibility. This fixes it by prepending a 145 // global cache path to relative paths. 146 // 147 // It is changed on DownloadIndexFile because that was the method that 148 // originally carried the cache path. 149 cp := r.Config.Cache 150 if !filepath.IsAbs(cp) { 151 cp = filepath.Join(cachePath, cp) 152 } 153 154 return ioutil.WriteFile(cp, index, 0644) 155 } 156 157 // Index generates an index for the chart repository and writes an index.yaml file. 158 func (r *ChartRepository) Index() error { 159 err := r.generateIndex() 160 if err != nil { 161 return err 162 } 163 return r.saveIndexFile() 164 } 165 166 func (r *ChartRepository) saveIndexFile() error { 167 index, err := yaml.Marshal(r.IndexFile) 168 if err != nil { 169 return err 170 } 171 return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644) 172 } 173 174 func (r *ChartRepository) generateIndex() error { 175 for _, path := range r.ChartPaths { 176 ch, err := loader.Load(path) 177 if err != nil { 178 return err 179 } 180 181 digest, err := provenance.DigestFile(path) 182 if err != nil { 183 return err 184 } 185 186 if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) { 187 r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest) 188 } 189 // TODO: If a chart exists, but has a different Digest, should we error? 190 } 191 r.IndexFile.SortEntries() 192 return nil 193 } 194 195 // FindChartInRepoURL finds chart in chart repository pointed by repoURL 196 // without adding repo to repositories 197 func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { 198 return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters) 199 } 200 201 // FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL 202 // without adding repo to repositories, like FindChartInRepoURL, 203 // but it also receives credentials for the chart repository. 204 func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { 205 206 // Download and write the index file to a temporary location 207 tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file") 208 if err != nil { 209 return "", errors.Errorf("cannot write index file for repository requested") 210 } 211 defer os.Remove(tempIndexFile.Name()) 212 213 c := Entry{ 214 URL: repoURL, 215 Username: username, 216 Password: password, 217 CertFile: certFile, 218 KeyFile: keyFile, 219 CAFile: caFile, 220 } 221 r, err := NewChartRepository(&c, getters) 222 if err != nil { 223 return "", err 224 } 225 if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil { 226 return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL) 227 } 228 229 // Read the index file for the repository to get chart information and return chart URL 230 repoIndex, err := LoadIndexFile(tempIndexFile.Name()) 231 if err != nil { 232 return "", err 233 } 234 235 errMsg := fmt.Sprintf("chart %q", chartName) 236 if chartVersion != "" { 237 errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion) 238 } 239 cv, err := repoIndex.Get(chartName, chartVersion) 240 if err != nil { 241 return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL) 242 } 243 244 if len(cv.URLs) == 0 { 245 return "", errors.Errorf("%s has no downloadable URLs", errMsg) 246 } 247 248 chartURL := cv.URLs[0] 249 250 absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) 251 if err != nil { 252 return "", errors.Wrap(err, "failed to make chart URL absolute") 253 } 254 255 return absoluteChartURL, nil 256 } 257 258 // ResolveReferenceURL resolves refURL relative to baseURL. 259 // If refURL is absolute, it simply returns refURL. 260 func ResolveReferenceURL(baseURL, refURL string) (string, error) { 261 parsedBaseURL, err := url.Parse(baseURL) 262 if err != nil { 263 return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL) 264 } 265 266 parsedRefURL, err := url.Parse(refURL) 267 if err != nil { 268 return "", errors.Wrapf(err, "failed to parse %s as URL", refURL) 269 } 270 271 return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil 272 }