diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index 9c1f25614..79c526cf7 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -52,6 +52,8 @@ type fetchCmd struct { destdir string version string repoURL string + username string + password string verify bool verifyLater bool @@ -138,14 +140,14 @@ func (f *fetchCmd) run() error { } if f.repoURL != "" { - chartURL, err := repo.FindChartInRepoURL(f.repoURL, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings)) + chartURL, err := repo.FindChartInRepoURL(f.repoURL, f.username, f.password, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings)) if err != nil { return err } f.chartRef = chartURL } - saved, v, err := c.DownloadTo(f.chartRef, f.version, dest) + saved, v, err := c.DownloadTo(f.chartRef, f.username, f.password, f.version, dest) if err != nil { return err } diff --git a/cmd/helm/inspect.go b/cmd/helm/inspect.go index 736f14fce..02eaf9b87 100644 --- a/cmd/helm/inspect.go +++ b/cmd/helm/inspect.go @@ -59,6 +59,8 @@ type inspectCmd struct { out io.Writer version string repoURL string + username string + password string certFile string keyFile string @@ -88,7 +90,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { if err := checkArgsLength(len(args), "chart name"); err != nil { return err } - cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring, + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, insp.certFile, insp.keyFile, insp.caFile) if err != nil { return err @@ -107,7 +109,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { if err := checkArgsLength(len(args), "chart name"); err != nil { return err } - cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring, + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, insp.certFile, insp.keyFile, insp.caFile) if err != nil { return err @@ -126,7 +128,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { if err := checkArgsLength(len(args), "chart name"); err != nil { return err } - cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring, + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, insp.certFile, insp.keyFile, insp.caFile) if err != nil { return err @@ -181,6 +183,18 @@ func newInspectCmd(out io.Writer) *cobra.Command { subCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc) } + username := "username" + usernamedesc := "chart repository username where to locate the requested chart" + inspectCommand.Flags().StringVar(&insp.username, username, "", usernamedesc) + valuesSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc) + chartSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc) + + password := "password" + passworddesc := "chart repository password where to locate the requested chart" + inspectCommand.Flags().StringVar(&insp.password, password, "", passworddesc) + valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) + chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) + certFile := "cert-file" certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle" for _, subCmd := range cmds { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 3b6d3e076..a4ba3f47d 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -118,6 +118,8 @@ type installCmd struct { timeout int64 wait bool repoURL string + username string + password string devel bool depUp bool @@ -165,7 +167,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { inst.version = ">0.0.0-0" } - cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring, + cp, err := locateChartPath(inst.repoURL, inst.username, inst.password, args[0], inst.version, inst.verify, inst.keyring, inst.certFile, inst.keyFile, inst.caFile) if err != nil { return err @@ -191,6 +193,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") @@ -381,8 +385,8 @@ func (i *installCmd) printRelease(rel *release.Release) { // - URL // // If 'verify' is true, this will attempt to also verify the chart. -func locateChartPath(repoURL, name, version string, verify bool, keyring, - certFile, keyFile, caFile string) (string, error) { +func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring, +certFile, keyFile, caFile string) (string, error) { name = strings.TrimSpace(name) version = strings.TrimSpace(version) if fi, err := os.Stat(name); err == nil { @@ -419,7 +423,7 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring, dl.Verify = downloader.VerifyAlways } if repoURL != "" { - chartURL, err := repo.FindChartInRepoURL(repoURL, name, version, + chartURL, err := repo.FindChartInRepoURL(repoURL, username, password, name, version, certFile, keyFile, caFile, getter.All(settings)) if err != nil { return "", err @@ -431,7 +435,7 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring, os.MkdirAll(settings.Home.Archive(), 0744) } - filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) + filename, _, err := dl.DownloadTo(name, username, password, version, settings.Home.Archive()) if err == nil { lname, err := filepath.Abs(filename) if err != nil { diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index b172d016c..fcaf32000 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -30,6 +30,8 @@ import ( type repoAddCmd struct { name string url string + username string + password string home helmpath.Home noupdate bool @@ -44,15 +46,31 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { add := &repoAddCmd{out: out} cmd := &cobra.Command{ - Use: "add [flags] [NAME] [URL]", + Use: "add [flags] [NAME] [URL] [USERNAME] [PASSWORD]", Short: "add a chart repository", RunE: func(cmd *cobra.Command, args []string) error { - if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil { + argsDesc := []string { + "name for the chart repository", + "the url of the chart repository", + "the username for the chart repository", + "the password of the chart repository", + } + + if len(args) <= 2 { + if err := checkArgsLength(len(args), argsDesc[0], argsDesc[1]); err != nil { + return err + } + } else + if err := checkArgsLength(len(args), argsDesc[0], argsDesc[1], argsDesc[2], argsDesc[3]); err != nil { return err } add.name = args[0] add.url = args[1] + if len(args) == 4 { + add.username = args[2] + add.password = args[3] + } add.home = settings.Home return add.run() @@ -69,14 +87,14 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { } func (a *repoAddCmd) run() error { - if err := addRepository(a.name, a.url, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil { + if err := addRepository(a.name, a.url, a.username, a.password , a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil { return err } fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name) return nil } -func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { +func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { f, err := repo.LoadRepositoriesFile(home.RepositoryFile()) if err != nil { return err @@ -91,6 +109,8 @@ func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFi Name: name, Cache: cif, URL: url, + Username: username, + Password: password, CertFile: certFile, KeyFile: keyFile, CAFile: caFile, diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index d08dd62ef..aadb59ac2 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -73,6 +73,8 @@ type upgradeCmd struct { reuseValues bool wait bool repoURL string + username string + password string devel bool certFile string @@ -128,6 +130,8 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored.") f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") @@ -139,7 +143,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { } func (u *upgradeCmd) run() error { - chartPath, err := locateChartPath(u.repoURL, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile) + chartPath, err := locateChartPath(u.repoURL, u.username, u.password, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile) if err != nil { return err } diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index c7610aaf9..38de99010 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -80,13 +80,13 @@ 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) { +func (c *ChartDownloader) DownloadTo(ref, username, password, version, dest string) (string, *provenance.Verification, error) { u, g, err := c.ResolveChartVersion(ref, version) if err != nil { return "", nil, err } - data, err := g.Get(u.String()) + data, err := g.GetWithCredentials(u.String(), username, password) if err != nil { return "", nil, err } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 0100772e9..682ceaa2e 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -195,7 +195,7 @@ func TestDownloadTo(t *testing.T) { Getters: getter.All(environment.EnvSettings{}), } cname := "/signtest-0.1.0.tgz" - where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) + where, v, err := c.DownloadTo(srv.URL()+cname, "", "", "", dest) if err != nil { t.Error(err) return @@ -258,7 +258,7 @@ func TestDownloadTo_VerifyLater(t *testing.T) { Getters: getter.All(environment.EnvSettings{}), } cname := "/signtest-0.1.0.tgz" - where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) + where, _, err := c.DownloadTo(srv.URL()+cname,"", "", "", dest) if err != nil { t.Error(err) return diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 6d4103bae..bac5c4823 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -245,13 +245,13 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { // Any failure to resolve/download a chart should fail: // https://github.com/kubernetes/helm/issues/1439 - churl, err := findChartURL(dep.Name, dep.Version, dep.Repository, repos) + churl, username, password, err := findChartURL(dep.Name, dep.Version, dep.Repository, repos) if err != nil { saveError = fmt.Errorf("could not find %s: %s", churl, err) break } - if _, _, err := dl.DownloadTo(churl, "", destPath); err != nil { + if _, _, err := dl.DownloadTo(churl, username, password, "", destPath); err != nil { saveError = fmt.Errorf("could not download %s: %s", churl, err) break } @@ -476,22 +476,30 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { // repoURL is the repository to search // // If it finds a URL that is "relative", it will prepend the repoURL. -func findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (string, error) { +func findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, err error) { for _, cr := range repos { if urlutil.Equal(repoURL, cr.Config.URL) { - entry, err := findEntryByName(name, cr) + var entry repo.ChartVersions + entry, err = findEntryByName(name, cr) if err != nil { - return "", err + return } - ve, err := findVersionedEntry(version, entry) + var ve *repo.ChartVersion + ve, err = findVersionedEntry(version, entry) if err != nil { - return "", err + return } - - return normalizeURL(repoURL, ve.URLs[0]) + url, err = normalizeURL(repoURL, ve.URLs[0]) + if err != nil { + return + } + username = cr.Config.Username + password = cr.Config.Password + return } } - return "", fmt.Errorf("chart %s not found in %s", name, repoURL) + err = fmt.Errorf("chart %s not found in %s", name, repoURL) + return } // findEntryByName finds an entry in the chart repository whose name matches the given name. diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 091277689..1ff2a9c17 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -77,14 +77,19 @@ func TestFindChartURL(t *testing.T) { version := "0.1.0" repoURL := "http://example.com/charts" - churl, err := findChartURL(name, version, repoURL, repos) + churl, username, password, err := findChartURL(name, version, repoURL, repos) if err != nil { t.Fatal(err) } if churl != "https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz" { t.Errorf("Unexpected URL %q", churl) } - + if username != "" { + t.Errorf("Unexpected username %q", username) + } + if password != "" { + t.Errorf("Unexpected password %q", password) + } } func TestGetRepoNames(t *testing.T) { diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index ca018884a..de9d59578 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -27,6 +27,8 @@ import ( type Getter interface { //Get file content by url string Get(url string) (*bytes.Buffer, error) + //Get file content by url, username and password strings + GetWithCredentials(href, username, password string) (*bytes.Buffer, error) } // Constructor is the function for every getter which creates a specific instance diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index c86b96458..5748ae9f7 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -34,6 +34,15 @@ type httpGetter struct { //Get performs a Get from repo.Getter and returns the body. func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { + return g.get(href, "", "") +} + +//Get performs a Get from repo.Getter using credentials and returns the body. +func (g *httpGetter) GetWithCredentials(href, username, password string) (*bytes.Buffer, error) { + return g.get(href, username, password) +} + +func (g *httpGetter) get(href, username, password string) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) // Set a helm specific user agent so that a repo server and metrics can @@ -44,6 +53,10 @@ func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { } req.Header.Set("User-Agent", "Helm/"+strings.TrimPrefix(version.GetVersion(), "v")) + if username != "" && password != "" { + req.SetBasicAuth(username, password) + } + resp, err := g.client.Do(req) if err != nil { return buf, err diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index c747eef7f..edbce131b 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -79,6 +79,10 @@ func (p *pluginGetter) Get(href string) (*bytes.Buffer, error) { return buf, nil } +func (p *pluginGetter) GetWithCredentials(href, username, password string) (*bytes.Buffer, error) { + return p.Get(href) +} + // newPluginGetter constructs a valid plugin getter func newPluginGetter(command string, settings environment.EnvSettings, name, base string) Constructor { return func(URL, CertFile, KeyFile, CAFile string) (Getter, error) { diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index ca1a71e3e..ca409fdad 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -35,6 +35,8 @@ type TestHTTPGetter struct { func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } +func (t *TestHTTPGetter) GetWithCredentials(href, username, password string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } + // Fake plugin tarball data var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index b95a7ae07..a4853ba22 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -29,6 +29,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/provenance" + "bytes" ) // Entry represents a collection of parameters for chart repository @@ -36,6 +37,8 @@ type Entry struct { Name string `json:"name"` Cache string `json:"cache"` URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` CertFile string `json:"certFile"` KeyFile string `json:"keyFile"` CAFile string `json:"caFile"` @@ -117,7 +120,13 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" indexURL = parsedURL.String() - resp, err := r.Client.Get(indexURL) + var resp *bytes.Buffer + if r.Config.Username == "" || r.Config.Password == "" { + resp, err = r.Client.Get(indexURL) + } else { + resp, err = r.Client.GetWithCredentials(indexURL, r.Config.Username, r.Config.Password) + } + if err != nil { return err } @@ -185,7 +194,7 @@ func (r *ChartRepository) generateIndex() error { // FindChartInRepoURL finds chart in chart repository pointed by repoURL // without adding repo to repositories -func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { +func FindChartInRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { // Download and write the index file to a temporary location tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file") @@ -196,6 +205,8 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF c := Entry{ URL: repoURL, + Username: username, + Password: password, CertFile: certFile, KeyFile: keyFile, CAFile: caFile,