github.com/wangchanggan/helm@v0.0.0-20211020154240-11b1b7d5406d/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 // 首先创建一个临时文件,用来存放index.yaml 210 tempIndexFile,err := ioutil.TempFile("","tmp-repo-file") 211 if err != nil { 212 return "", fmt.Errorf("cannot write index file for repository requested") 213 } 214 // 退出后将这个临时文件删除 215 defer os.Remove(tempIndexFile.Name()) 216 217 // 将repoURL、用户名、密码等设置到对象中,方便后面下载 218 c := Entry{ 219 URL: repoURL, 220 Username: username, 221 Password: password, 222 CertFile: certFile, 223 KeyFile: keyFile, 224 CAFile: caFile, 225 } 226 227 // 创建对应的 Chart repo实例对象 228 r, err := NewChartRepository(&c, getters) 229 if err != nil { 230 return "", err 231 } 232 233 // 将URL对应的index.yaml文件下载到本地的临时文件中 234 if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil { 235 return "", fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", repoURL, err) 236 } 237 238 // Read the index file for the repository to get chart information and return chart URL 239 // 读取该文件,并寻找对应的Chart和URL 240 repoIndex, err := LoadIndexFile(tempIndexFile.Name()) 241 if err != nil { 242 return "", err 243 } 244 245 // 将index.yaml内提供的Chart名字和URL拼接好,形成一个完整路径的文件 246 errMsg := fmt.Sprintf("chart %q", chartName) 247 if chartVersion != "" { 248 errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion) 249 } 250 cv, err := repoIndex.Get(chartName, chartVersion) 251 if err != nil { 252 return "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL) 253 } 254 255 if len(cv.URLs) == 0 { 256 return "", fmt.Errorf("%s has no downloadable URLs", errMsg) 257 } 258 259 chartURL := cv.URLs[0] 260 261 absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) 262 if err != nil { 263 return "", fmt.Errorf("failed to make chart URL absolute: %v", err) 264 } 265 266 // 返回对应的全局含有URL路径的文件 267 return absoluteChartURL, nil 268 } 269 270 // ResolveReferenceURL resolves refURL relative to baseURL. 271 // If refURL is absolute, it simply returns refURL. 272 func ResolveReferenceURL(baseURL, refURL string) (string, error) { 273 parsedBaseURL, err := url.Parse(baseURL) 274 if err != nil { 275 return "", fmt.Errorf("failed to parse %s as URL: %v", baseURL, err) 276 } 277 278 parsedRefURL, err := url.Parse(refURL) 279 if err != nil { 280 return "", fmt.Errorf("failed to parse %s as URL: %v", refURL, err) 281 } 282 283 // We need a trailing slash for ResolveReference to work, but make sure there isn't already one 284 parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/" 285 resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL) 286 // if the base URL contains query string parameters, 287 // propagate them to the child URL but only if the 288 // refURL is relative to baseURL 289 if (resolvedURL.Hostname() == parsedBaseURL.Hostname()) && (resolvedURL.Port() == parsedBaseURL.Port()) { 290 resolvedURL.RawQuery = parsedBaseURL.RawQuery 291 } 292 293 return resolvedURL.String(), nil 294 }