Get http.Client from referenced repository

pull/1766/head
Anton Galitsyn 8 years ago
parent b0e7a43b5b
commit 89ab7555db

@ -76,12 +76,12 @@ 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) {
// resolve URL
u, err := c.ResolveChartVersion(ref, version)
u, client, err := c.ResolveChartVersion(ref, version)
if err != nil {
return "", nil, err
}
data, err := download(u.String())
data, err := download(u.String(), client)
if err != nil {
return "", nil, err
}
@ -95,8 +95,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
// If provenance is requested, verify it.
ver := &provenance.Verification{}
if c.Verify > VerifyNever {
body, err := download(u.String() + ".prov")
body, err := download(u.String()+".prov", client)
if err != nil {
if c.Verify == VerifyAlways {
return destfile, ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov")
@ -132,63 +131,75 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
// * 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, error) {
// See if it's already a full URL.
// FIXME: Why do we use url.ParseRequestURI instead of url.Parse?
u, err := url.ParseRequestURI(ref)
if err == nil {
// If it has a scheme and host and path, it's a full URL
if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 {
return u, nil
}
return u, fmt.Errorf("invalid chart url format: %s", ref)
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, *http.Client, error) {
u, err := url.Parse(ref)
if err != nil {
return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref)
}
r, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile())
rf, err := repo.LoadRepositoryFile(c.HelmHome.RepositoryFile())
if err != nil {
return u, err
return nil, nil, err
}
// See if it's of the form: repo/path_to_chart
p := strings.SplitN(ref, "/", 2)
if len(p) < 2 {
return u, fmt.Errorf("invalid chart url format: %s", ref)
var (
chartName string
rc *repo.ChartRepositoryConfig
)
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)
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)
}
repoName := p[0]
chartName = p[1]
rc, err = pickChartRepositoryConfigByName(repoName, rf.Repositories)
if err != nil {
return nil, nil, err
}
}
repoName := p[0]
chartName := p[1]
rf, err := findRepoEntry(repoName, r.Repositories)
r, err := repo.NewChartRepository(rc)
if err != nil {
return u, err
}
if rf.URL == "" {
return u, fmt.Errorf("no URL found for repository %q", repoName)
return nil, nil, err
}
// Next, we need to load the index, and actually look up the chart.
i, err := repo.NewChartRepositoryIndexFromFile(c.HelmHome.CacheIndex(repoName))
i, err := repo.NewChartRepositoryIndexFromFile(c.HelmHome.CacheIndex(r.Config.Name))
if err != nil {
return u, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
return nil, nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
}
cv, err := i.Get(chartName, version)
if err != nil {
return u, fmt.Errorf("chart %q not found in %s index. (try 'helm repo update'). %s", chartName, repoName, err)
return nil, nil, 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 u, fmt.Errorf("chart %q has no downloadable URLs", ref)
return nil, nil, fmt.Errorf("chart %q has no downloadable URLs", ref)
}
return url.Parse(cv.URLs[0])
}
func findRepoEntry(name string, repos []*repo.ChartRepositoryConfig) (*repo.ChartRepositoryConfig, error) {
for _, re := range repos {
if re.Name == name {
return re, nil
}
// 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 nil, fmt.Errorf("no repo named %q", name)
return u, r.Client, nil
}
// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart.
@ -217,11 +228,11 @@ func VerifyChart(path string, keyring string) (*provenance.Verification, error)
return sig.Verify(path, provfile)
}
// download performs a simple HTTP Get and returns the body.
func download(href string) (*bytes.Buffer, error) {
// download performs a HTTP Get using specified client and returns the body.
func download(href string, client *http.Client) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
resp, err := http.Get(href)
resp, err := client.Get(href)
if err != nil {
return buf, err
}
@ -241,3 +252,24 @@ func download(href string) (*bytes.Buffer, error) {
func isTar(filename string) bool {
return strings.ToLower(filepath.Ext(filename)) == ".tgz"
}
func pickChartRepositoryConfigByName(name string, cfgs []*repo.ChartRepositoryConfig) (*repo.ChartRepositoryConfig, error) {
for _, rc := range cfgs {
if rc.Name == name {
if rc.URL == "" {
return nil, fmt.Errorf("no URL found for repository %s", name)
}
return rc, nil
}
}
return nil, fmt.Errorf("repo %s not found", name)
}
func pickChartRepositoryConfigByURL(u string, cfgs []*repo.ChartRepositoryConfig) (*repo.ChartRepositoryConfig, error) {
for _, rc := range cfgs {
if rc.URL == u {
return rc, nil
}
}
return nil, fmt.Errorf("repo with URL %s not found", u)
}

@ -37,9 +37,9 @@ func TestResolveChartRef(t *testing.T) {
{name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"},
{name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"},
{name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"},
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz"},
{name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"},
{name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"},
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true},
{name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true},
{name: "invalid", ref: "invalid-1.2.3", fail: true},
{name: "not found", ref: "nosuchthing/invalid-1.2.3", fail: true},
@ -51,7 +51,7 @@ func TestResolveChartRef(t *testing.T) {
}
for _, tt := range tests {
u, err := c.ResolveChartVersion(tt.ref, tt.version)
u, _, err := c.ResolveChartVersion(tt.ref, tt.version)
if err != nil {
if tt.fail {
continue
@ -84,7 +84,7 @@ func TestDownload(t *testing.T) {
}))
defer srv.Close()
got, err := download(srv.URL)
got, err := download(srv.URL, http.DefaultClient)
if err != nil {
t.Fatal(err)
}
@ -105,7 +105,7 @@ func TestDownload(t *testing.T) {
u, _ := url.ParseRequestURI(basicAuthSrv.URL)
u.User = url.UserPassword("username", "password")
got, err = download(u.String())
got, err = download(u.String(), http.DefaultClient)
if err != nil {
t.Fatal(err)
}
@ -133,25 +133,43 @@ func TestIsTar(t *testing.T) {
}
func TestDownloadTo(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-downloadto-")
tmp, err := ioutil.TempDir("", "helm-downloadto-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hh)
defer os.RemoveAll(tmp)
dest := filepath.Join(hh, "dest")
os.MkdirAll(dest, 0755)
hh := helmpath.Home(tmp)
dest := filepath.Join(hh.String(), "dest")
configDirectories := []string{
hh.String(),
hh.Repository(),
hh.Cache(),
dest,
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatalf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
t.Fatalf("%s must be a directory", p)
}
}
// Set up a fake repo
srv := repotest.NewServer(hh)
srv := repotest.NewServer(tmp)
defer srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil {
t.Error(err)
return
}
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
c := ChartDownloader{
HelmHome: helmpath.Home("testdata/helmhome"),
HelmHome: hh,
Out: os.Stderr,
Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub",
@ -178,25 +196,43 @@ func TestDownloadTo(t *testing.T) {
}
func TestDownloadTo_VerifyLater(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-downloadto-")
tmp, err := ioutil.TempDir("", "helm-downloadto-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hh)
defer os.RemoveAll(tmp)
dest := filepath.Join(hh, "dest")
os.MkdirAll(dest, 0755)
hh := helmpath.Home(tmp)
dest := filepath.Join(hh.String(), "dest")
configDirectories := []string{
hh.String(),
hh.Repository(),
hh.Cache(),
dest,
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatalf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
t.Fatalf("%s must be a directory", p)
}
}
// Set up a fake repo
srv := repotest.NewServer(hh)
srv := repotest.NewServer(tmp)
defer srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil {
t.Error(err)
return
}
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
c := ChartDownloader{
HelmHome: helmpath.Home("testdata/helmhome"),
HelmHome: hh,
Out: os.Stderr,
Verify: VerifyLater,
}

@ -213,7 +213,7 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
// hasAllRepos ensures that all of the referenced deps are in the local repo cache.
func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())
rf, err := repo.LoadRepositoryFile(m.HelmHome.RepositoryFile())
if err != nil {
return err
}
@ -244,7 +244,7 @@ func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file.
func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string, error) {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())
rf, err := repo.LoadRepositoryFile(m.HelmHome.RepositoryFile())
if err != nil {
return nil, err
}
@ -277,7 +277,7 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string,
// UpdateRepositories updates all of the local repos to the latest.
func (m *Manager) UpdateRepositories() error {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())
rf, err := repo.LoadRepositoryFile(m.HelmHome.RepositoryFile())
if err != nil {
return err
}
@ -409,7 +409,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
repoyaml := m.HelmHome.RepositoryFile()
// Load repositories.yaml file
rf, err := repo.LoadRepositoriesFile(repoyaml)
rf, err := repo.LoadRepositoryFile(repoyaml)
if err != nil {
return indices, fmt.Errorf("failed to load %s: %s", repoyaml, err)
}

@ -0,0 +1,15 @@
apiVersion: v1
entries:
foo:
- name: foo
description: Foo Chart
engine: gotpl
home: https://k8s.io/helm
keywords: []
maintainers: []
sources:
- https://github.com/kubernetes/charts
urls:
- http://username:password@example.com/foo-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d

@ -0,0 +1,15 @@
apiVersion: v1
entries:
foo:
- name: foo
description: Foo Chart
engine: gotpl
home: https://k8s.io/helm
keywords: []
maintainers: []
sources:
- https://github.com/kubernetes/charts
urls:
- https://example.com/foo-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d

@ -28,3 +28,16 @@ entries:
maintainers: []
engine: ""
icon: ""
foo:
- name: foo
description: Foo Chart
engine: gotpl
home: https://k8s.io/helm
keywords: []
maintainers: []
sources:
- https://github.com/kubernetes/charts
urls:
- http://example.com/foo-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d

@ -2,5 +2,9 @@ apiVersion: v1
repositories:
- name: testing
url: "http://example.com"
- name: testing-https
url: "https://example.com"
- name: testing-basicauth
url: "http://username:password@example.com"
- name: kubernetes-charts
url: "http://example.com/charts"

@ -270,7 +270,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
} else if fi.IsDir() {
return fmt.Errorf("%s must be a file, not a directory", repoFile)
}
if r, err := repo.LoadRepositoriesFile(repoFile); err == repo.ErrRepoOutOfDate {
if r, err := repo.LoadRepositoryFile(repoFile); err == repo.ErrRepoOutOfDate {
t.Log("Updating repository file format...")
if err := r.WriteFile(repoFile, 0644); err != nil {
return err

@ -55,7 +55,7 @@ To dump a manifest containing the Tiller deployment YAML, combine the
const (
stableRepository = "stable"
localRepository = "local"
stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com/"
stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com"
// This is the IPv4 loopback, not localhost, because we have to force IPv4
// for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410
localRepositoryURL = "http://127.0.0.1:8879/charts"
@ -236,7 +236,7 @@ func initLocalRepo(indexFile, cacheFile string) (*repo.ChartRepositoryConfig, er
}
func ensureRepoFileFormat(file string, out io.Writer) error {
r, err := repo.LoadRepositoriesFile(file)
r, err := repo.LoadRepositoryFile(file)
if err == repo.ErrRepoOutOfDate {
fmt.Fprintln(out, "Updating repository file format...")
if err := r.WriteFile(file, 0644); err != nil {

@ -78,7 +78,7 @@ func (a *repoAddCmd) run() error {
}
func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
f, err := repo.LoadRepositoryFile(home.RepositoryFile())
if err != nil {
return err
}

@ -84,7 +84,7 @@ func TestRepoAdd(t *testing.T) {
t.Error(err)
}
f, err := repo.LoadRepositoriesFile(hh.RepositoryFile())
f, err := repo.LoadRepositoryFile(hh.RepositoryFile())
if err != nil {
t.Error(err)
}

@ -51,7 +51,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
}
func (a *repoListCmd) run() error {
f, err := repo.LoadRepositoriesFile(a.home.RepositoryFile())
f, err := repo.LoadRepositoryFile(a.home.RepositoryFile())
if err != nil {
return err
}

@ -62,7 +62,7 @@ func (r *repoRemoveCmd) run() error {
func removeRepoLine(out io.Writer, name string, home helmpath.Home) error {
repoFile := home.RepositoryFile()
r, err := repo.LoadRepositoriesFile(repoFile)
r, err := repo.LoadRepositoryFile(repoFile)
if err != nil {
return err
}

@ -69,7 +69,7 @@ func TestRepoRemove(t *testing.T) {
t.Errorf("Error cache file was not removed for repository %s", testName)
}
f, err := repo.LoadRepositoriesFile(hh.RepositoryFile())
f, err := repo.LoadRepositoryFile(hh.RepositoryFile())
if err != nil {
t.Error(err)
}

@ -65,7 +65,7 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
}
func (u *repoUpdateCmd) run() error {
f, err := repo.LoadRepositoriesFile(u.home.RepositoryFile())
f, err := repo.LoadRepositoryFile(u.home.RepositoryFile())
if err != nil {
return err
}

@ -110,7 +110,7 @@ func (s *searchCmd) formatSearchResults(res []*search.Result) string {
func (s *searchCmd) buildIndex() (*search.Index, error) {
// Load the repositories.yaml
rf, err := repo.LoadRepositoriesFile(s.helmhome.RepositoryFile())
rf, err := repo.LoadRepositoryFile(s.helmhome.RepositoryFile())
if err != nil {
return nil, err
}

@ -29,6 +29,7 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/tlsutil"
"k8s.io/helm/pkg/urlutil"
)
// ChartRepositoryConfig represents a collection of parameters for chart repository
@ -58,6 +59,13 @@ func NewChartRepository(cfg *ChartRepositoryConfig) (*ChartRepository, error) {
return nil, fmt.Errorf("can't create TLS config for client: %s", err.Error())
}
tlsConf.BuildNameToCertificate()
sni, err := urlutil.ExtractHostname(cfg.URL)
if err != nil {
return nil, err
}
tlsConf.ServerName = sni
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,

@ -48,11 +48,11 @@ func NewRepositoryFile() *RepositoryFile {
}
}
// LoadRepositoriesFile takes a file at the given path and returns a RepositoryFile object
// LoadRepositoryFile takes a file at the given path and returns a RepositoryFile object
//
// If this returns ErrRepoOutOfDate, it also returns a recovered RepositoryFile that
// can be saved as a replacement to the out of date file.
func LoadRepositoriesFile(path string) (*RepositoryFile, error) {
func LoadRepositoryFile(path string) (*RepositoryFile, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err

@ -73,7 +73,7 @@ func TestNewRepositoriesFile(t *testing.T) {
},
)
repofile, err := LoadRepositoriesFile(testRepositoriesFile)
repofile, err := LoadRepositoryFile(testRepositoriesFile)
if err != nil {
t.Errorf("%q could not be loaded: %s", testRepositoriesFile, err)
}
@ -97,7 +97,7 @@ func TestNewRepositoriesFile(t *testing.T) {
}
func TestNewPreV1RepositoriesFile(t *testing.T) {
r, err := LoadRepositoriesFile("testdata/old-repositories.yaml")
r, err := LoadRepositoryFile("testdata/old-repositories.yaml")
if err != nil && err != ErrRepoOutOfDate {
t.Fatal(err)
}

@ -17,6 +17,7 @@ limitations under the License.
package urlutil
import (
"net"
"net/url"
"path"
"path/filepath"
@ -64,3 +65,17 @@ func URLAreEqual(a, b string) bool {
}
return au.String() == bu.String()
}
// ExtractHostname returns hostname from URL
func ExtractHostname(addr string) (string, error) {
u, err := url.Parse(addr)
if err != nil {
return "", err
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
return "", err
}
return host, nil
}

Loading…
Cancel
Save