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