github.com/koderover/helm@v2.17.0+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 "k8s.io/helm/pkg/repo" 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "net/url" 23 "os" 24 "path" 25 "path/filepath" 26 "strings" 27 28 "github.com/ghodss/yaml" 29 30 "k8s.io/helm/pkg/chartutil" 31 "k8s.io/helm/pkg/getter" 32 "k8s.io/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, fmt.Errorf("invalid chart URL format: %s", cfg.URL) 60 } 61 62 getterConstructor, err := getters.ByScheme(u.Scheme) 63 if err != nil { 64 return nil, fmt.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, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err) 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 fmt.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 parsedURL, err := url.Parse(r.Config.URL) 116 if err != nil { 117 return err 118 } 119 parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml") 120 parsedURL.Path = path.Join(parsedURL.Path, "index.yaml") 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 // We need a trailing slash for ResolveReference to work, but make sure there isn't already one 274 parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/" 275 resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL) 276 // if the base URL contains query string parameters, 277 // propagate them to the child URL but only if the 278 // refURL is relative to baseURL 279 if (resolvedURL.Hostname() == parsedBaseURL.Hostname()) && (resolvedURL.Port() == parsedBaseURL.Port()) { 280 resolvedURL.RawQuery = parsedBaseURL.RawQuery 281 } 282 283 return resolvedURL.String(), nil 284 }