@ -21,6 +21,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
@ -29,6 +30,7 @@ import (
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/urlutil"
)
// VerificationStrategy describes a strategy for determining whether to verify a chart.
@ -49,6 +51,9 @@ const (
VerifyLater
)
// ErrNoOwnerRepo indicates that a given chart URL can't be found in any repos.
var ErrNoOwnerRepo = errors . New ( "could not find a repo containing the given URL" )
// ChartDownloader handles downloading a chart.
//
// It is capable of performing verifications on charts as well.
@ -75,6 +80,7 @@ type ChartDownloader struct {
// Returns a string path to the location where the file was downloaded and a verification
// (if provenance was verified), or an error if something bad happened.
func ( c * ChartDownloader ) DownloadTo ( ref , version , dest string ) ( string , * provenance . Verification , error ) {
var r repo . Getter
u , r , err := c . ResolveChartVersion ( ref , version )
if err != nil {
return "" , nil , err
@ -121,16 +127,19 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
// ResolveChartVersion resolves a chart reference to a URL.
//
// It returns the URL as well as a preconfigured repo.Getter that can fetch
// the URL.
//
// A reference may be an HTTP URL, a 'reponame/chartname' reference, or a local path.
//
// A version is a SemVer string (1.2.3-beta.1+f334a6789).
//
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For a chart reference
// * If version is non-empty, this will return the URL for that version
// * If version is empty, this will return the URL for the latest version
// * If no version can be found, an error is returned
func ( c * ChartDownloader ) ResolveChartVersion ( ref , version string ) ( * url . URL , * repo . ChartRepository , error ) {
// * If no version can be found, an error is returned
func ( c * ChartDownloader ) ResolveChartVersion ( ref , version string ) ( * url . URL , repo . Getter , error ) {
u , err := url . Parse ( ref )
if err != nil {
return nil , nil , fmt . Errorf ( "invalid chart URL format: %s" , ref )
@ -138,64 +147,67 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, *r
rf , err := repo . LoadRepositoriesFile ( c . HelmHome . RepositoryFile ( ) )
if err != nil {
return nil , nil , err
return u , nil , err
}
var (
chartName string
rc * repo . Entry
)
if u . IsAbs ( ) && len ( u . Host ) > 0 && len ( u . Path ) > 0 {
// If it has a scheme and host and path, it's a full URL
p := strings . SplitN ( strings . TrimLeft ( u . Path , "/" ) , "-" , 2 )
if len ( p ) < 2 {
return nil , nil , fmt . Errorf ( "Seems that chart path is not in form of repo_url/path_to_chart, got: %s" , u )
}
chartName = p [ 0 ]
u . Path = ""
rc , err = pickChartRepositoryConfigByURL ( u . String ( ) , rf . Repositories )
// In this case, we have to find the parent repo that contains this chart
// URL. And this is an unfortunate problem, as it requires actually going
// through each repo cache file and finding a matching URL. But basically
// we want to find the repo in case we have special SSL cert config
// for that repo.
rc , err := c . scanReposForURL ( ref , rf )
if err != nil {
return nil , nil , err
}
} else {
// See if it's of the form: repo/path_to_chart
p := strings . SplitN ( u . Path , "/" , 2 )
if len ( p ) < 2 {
return nil , nil , fmt . Errorf ( "Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s" , u )
// If there is no special config, return the default HTTP client and
// swallow the error.
if err == ErrNoOwnerRepo {
return u , http . DefaultClient , nil
}
return u , nil , err
}
r , err := repo . NewChartRepository ( rc )
// If we get here, we don't need to go through the next phase of looking
// up the URL. We have it already. So we just return.
return u , r , err
}
repoName := p [ 0 ]
chartName = p [ 1 ]
rc , err = pickChartRepositoryConfigByName ( repoName , rf . Repositories )
if err != nil {
return nil , nil , err
}
// See if it's of the form: repo/path_to_chart
p := strings . SplitN ( u . Path , "/" , 2 )
if len ( p ) < 2 {
return u , nil , fmt . Errorf ( "Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s" , u )
}
repoName := p [ 0 ]
chartName := p [ 1 ]
rc , err := pickChartRepositoryConfigByName ( repoName , rf . Repositories )
if err != nil {
return u , nil , err
}
r , err := repo . NewChartRepository ( rc )
if err != nil {
return nil , nil , err
return u , nil , err
}
// Next, we need to load the index, and actually look up the chart.
i , err := repo . LoadIndexFile ( c . HelmHome . CacheIndex ( r . Config . Name ) )
if err != nil {
return nil , nil , fmt . Errorf ( "no cached repo found. (try 'helm repo update'). %s" , err )
return u , r , fmt . Errorf ( "no cached repo found. (try 'helm repo update'). %s" , err )
}
cv , err := i . Get ( chartName , version )
if err != nil {
return nil , nil , fmt . Errorf ( "chart %q not found in %s index. (try 'helm repo update'). %s" , chartName , r . Config . Name , err )
return u , r , fmt . Errorf ( "chart %q not found in %s index. (try 'helm repo update'). %s" , chartName , r . Config . Name , err )
}
if len ( cv . URLs ) == 0 {
return nil , nil , fmt . Errorf ( "chart %q has no downloadable URLs" , ref )
return u , r , fmt . Errorf ( "chart %q has no downloadable URLs" , ref )
}
// TODO: Seems that picking first URL is not fully correct
u , err = url . Parse ( cv . URLs [ 0 ] )
if err != nil {
return nil , nil , fmt . Errorf ( "invalid chart URL format: %s" , ref )
return u , r , fmt . Errorf ( "invalid chart URL format: %s" , ref )
}
return u , r , nil
@ -264,11 +276,48 @@ func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Ent
return nil , fmt . Errorf ( "repo %s not found" , name )
}
func pickChartRepositoryConfigByURL ( u string , cfgs [ ] * repo . Entry ) ( * repo . Entry , error ) {
for _ , rc := range cfgs {
if rc . URL == u {
return rc , nil
// scanReposForURL scans all repos to find which repo contains the given URL.
//
// This will attempt to find the given URL in all of the known repositories files.
//
// If the URL is found, this will return the repo entry that contained that URL.
//
// If all of the repos are checked, but the URL is not found, an ErrNoOwnerRepo
// error is returned.
//
// Other errors may be returned when repositories cannot be loaded or searched.
//
// Technically, the fact that a URL is not found in a repo is not a failure indication.
// Charts are not required to be included in an index before they are valid. So
// be mindful of this case.
//
// The same URL can technically exist in two or more repositories. This algorithm
// will return the first one it finds. Order is determined by the order of repositories
// in the repositories.yaml file.
func ( c * ChartDownloader ) scanReposForURL ( u string , rf * repo . RepoFile ) ( * repo . Entry , error ) {
// FIXME: This is far from optimal. Larger installations and index files will
// incur a performance hit for this type of scanning.
for _ , rc := range rf . Repositories {
r , err := repo . NewChartRepository ( rc )
if err != nil {
return nil , err
}
i , err := repo . LoadIndexFile ( c . HelmHome . CacheIndex ( r . Config . Name ) )
if err != nil {
return nil , fmt . Errorf ( "no cached repo found. (try 'helm repo update'). %s" , err )
}
for _ , entry := range i . Entries {
for _ , ver := range entry {
for _ , dl := range ver . URLs {
if urlutil . Equal ( u , dl ) {
return rc , nil
}
}
}
}
}
return nil , fmt . Errorf ( "repo with URL %s not found" , u )
// This means that there is no repo file for the given URL.
return nil , ErrNoOwnerRepo
}