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  }