diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index 9c1f25614..069f57eff 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 @@ -106,6 +108,8 @@ func newFetchCmd(out io.Writer) *cobra.Command { f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") + f.StringVar(&fch.username, "username", "", "chart repository username") + f.StringVar(&fch.password, "password", "", "chart repository password") return cmd } @@ -117,6 +121,8 @@ func (f *fetchCmd) run() error { Keyring: f.keyring, Verify: downloader.VerifyNever, Getters: getter.All(settings), + Username: f.username, + Password: f.password, } if f.verify { @@ -138,7 +144,7 @@ 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.FindChartInAuthRepoURL(f.repoURL, f.username, f.password, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings)) if err != nil { return err } diff --git a/cmd/helm/inspect.go b/cmd/helm/inspect.go index 736f14fce..999856959 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 @@ -145,7 +147,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..a02f543db 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,7 +385,7 @@ 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, +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) @@ -414,12 +418,14 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring, Out: os.Stdout, Keyring: keyring, Getters: getter.All(settings), + Username: username, + Password: password, } if verify { dl.Verify = downloader.VerifyAlways } if repoURL != "" { - chartURL, err := repo.FindChartInRepoURL(repoURL, name, version, + chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version, certFile, keyFile, caFile, getter.All(settings)) if err != nil { return "", err diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index b172d016c..77a64cc89 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 @@ -60,6 +62,8 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { } f := cmd.Flags() + f.StringVar(&add.username, "username", "", "chart repository username") + f.StringVar(&add.password, "password", "", "chart repository password") f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered") f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") @@ -69,14 +73,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 +95,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/repo_add_test.go b/cmd/helm/repo_add_test.go index 79d0d3c53..157b1cc5b 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -80,7 +80,7 @@ func TestRepoAdd(t *testing.T) { settings.Home = thome - if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil { + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil { t.Error(err) } @@ -93,11 +93,11 @@ func TestRepoAdd(t *testing.T) { t.Errorf("%s was not successfully inserted into %s", testName, hh.RepositoryFile()) } - if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil { + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil { t.Errorf("Repository was not updated: %s", err) } - if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil { + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil { t.Errorf("Duplicate repository name was added") } } diff --git a/cmd/helm/repo_remove_test.go b/cmd/helm/repo_remove_test.go index 63082b42e..174a44495 100644 --- a/cmd/helm/repo_remove_test.go +++ b/cmd/helm/repo_remove_test.go @@ -51,7 +51,7 @@ func TestRepoRemove(t *testing.T) { if err := removeRepoLine(b, testName, hh); err == nil { t.Errorf("Expected error removing %s, but did not get one.", testName) } - if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil { + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil { t.Error(err) } 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/docs/chart_repository.md b/docs/chart_repository.md index 1379573fc..5a24249b8 100644 --- a/docs/chart_repository.md +++ b/docs/chart_repository.md @@ -148,6 +148,11 @@ Charts repository hosts its charts, so you may want to take a **Note:** A public GCS bucket can be accessed via simple HTTPS at this address `https://bucket-name.storage.googleapis.com/`. +### JFrog Artifactory + +You can also set up chart repositories using JFrog Artifactory. +Read more about chart repositories with JFrog Artifactory [here](https://www.jfrog.com/confluence/display/RTF/Helm+Chart+Repositories) + ### Github Pages example In a similar way you can create charts repository using GitHub Pages. @@ -270,10 +275,10 @@ fantastic-charts https://fantastic-charts.storage.googleapis.com If the charts are backed by HTTP basic authentication, you can also supply the username and password here: -```console -$ helm repo add fantastic-charts https://username:password@fantastic-charts.storage.googleapis.com +``console +$ helm repo add fantastic-charts https://fantastic-charts.storage.googleapis.com --username my-username --password my-password $ helm repo list -fantastic-charts https://username:password@fantastic-charts.storage.googleapis.com +fantastic-charts https://fantastic-charts.storage.googleapis.com ``` **Note:** A repository will not be added if it does not contain a valid diff --git a/docs/helm/helm_fetch.md b/docs/helm/helm_fetch.md index 3bc3334a0..1ddef65fa 100644 --- a/docs/helm/helm_fetch.md +++ b/docs/helm/helm_fetch.md @@ -33,10 +33,12 @@ helm fetch [flags] [chart URL | repo/chartname] [...] --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. --key-file string identify HTTPS client using this SSL key file --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password --prov fetch the provenance file, but don't perform verification --repo string chart repository url where to locate the requested chart --untar if set to true, will untar the chart after downloading it --untardir string if untar is specified, this flag specifies the name of the directory into which the chart is expanded (default ".") + --username string chart repository username --verify verify the package against its signature --version string specific version of a chart. Without this, the latest version is fetched ``` @@ -54,5 +56,3 @@ helm fetch [flags] [chart URL | repo/chartname] [...] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. - -###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/docs/helm/helm_inspect.md b/docs/helm/helm_inspect.md index 9928ed847..e46b3dbf4 100644 --- a/docs/helm/helm_inspect.md +++ b/docs/helm/helm_inspect.md @@ -23,7 +23,9 @@ helm inspect [CHART] --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle --key-file string identify HTTPS client using this SSL key file --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart --verify verify the provenance data for this chart --version string version of the chart. By default, the newest chart is shown ``` diff --git a/docs/helm/helm_inspect_chart.md b/docs/helm/helm_inspect_chart.md index bfa6061c8..cd1328b59 100644 --- a/docs/helm/helm_inspect_chart.md +++ b/docs/helm/helm_inspect_chart.md @@ -21,7 +21,9 @@ helm inspect chart [CHART] --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle --key-file string identify HTTPS client using this SSL key file --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart --verify verify the provenance data for this chart --version string version of the chart. By default, the newest chart is shown ``` diff --git a/docs/helm/helm_inspect_values.md b/docs/helm/helm_inspect_values.md index fbf8660c2..6a907cc7d 100644 --- a/docs/helm/helm_inspect_values.md +++ b/docs/helm/helm_inspect_values.md @@ -21,7 +21,9 @@ helm inspect values [CHART] --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle --key-file string identify HTTPS client using this SSL key file --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart --verify verify the provenance data for this chart --version string version of the chart. By default, the newest chart is shown ``` diff --git a/docs/helm/helm_install.md b/docs/helm/helm_install.md index 406416dc8..0ae9097ba 100644 --- a/docs/helm/helm_install.md +++ b/docs/helm/helm_install.md @@ -80,6 +80,7 @@ helm install [CHART] --name-template string specify template used to name the release --namespace string namespace to install the release into. Defaults to the current kube config namespace. --no-hooks prevent hooks from running during install + --password string chart repository password where to locate the requested chart --replace re-use the given name, even if that name is already used. This is unsafe in production --repo string chart repository url where to locate the requested chart --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) @@ -89,6 +90,7 @@ helm install [CHART] --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-verify enable TLS for request and verify remote + --username string chart repository username where to locate the requested chart -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) --verify verify the package before installing it --version string specify the exact chart version to install. If this is not specified, the latest version is installed diff --git a/docs/helm/helm_repo_add.md b/docs/helm/helm_repo_add.md index f0dfcbd5d..456ffa27e 100644 --- a/docs/helm/helm_repo_add.md +++ b/docs/helm/helm_repo_add.md @@ -18,6 +18,8 @@ helm repo add [flags] [NAME] [URL] --cert-file string identify HTTPS client using this SSL certificate file --key-file string identify HTTPS client using this SSL key file --no-update raise error if repo is already registered + --password string chart repository password + --username string chart repository username ``` ### Options inherited from parent commands diff --git a/docs/helm/helm_upgrade.md b/docs/helm/helm_upgrade.md index 5ed128958..5cbb9a110 100644 --- a/docs/helm/helm_upgrade.md +++ b/docs/helm/helm_upgrade.md @@ -46,6 +46,7 @@ helm upgrade [RELEASE] [CHART] --keyring string path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg") --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace --no-hooks disable pre/post upgrade hooks + --password string chart repository password where to locate the requested chart --recreate-pods performs pods restart for the resource if applicable --repo string chart repository url where to locate the requested chart --reset-values when upgrading, reset the values to the ones built into the chart @@ -57,6 +58,7 @@ helm upgrade [RELEASE] [CHART] --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-verify enable TLS for request and verify remote + --username string chart repository username where to locate the requested chart -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) --verify verify the provenance of the chart before upgrading --version string specify the exact chart version to use. If this is not specified, the latest version is used diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index c7610aaf9..9fe89820e 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -67,6 +67,10 @@ type ChartDownloader struct { HelmHome helmpath.Home // Getter collection for the operation Getters getter.Providers + // Chart repository username + Username string + // Chart repository password + Password string } // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. @@ -81,7 +85,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) { - u, g, err := c.ResolveChartVersion(ref, version) + u, r, g, err := c.ResolveChartVersionAndGetRepo(ref, version) if err != nil { return "", nil, err } @@ -100,7 +104,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 := g.Get(u.String() + ".prov") + body, err := r.Client.Get(u.String() + ".prov") if err != nil { if c.Verify == VerifyAlways { return destfile, ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov") @@ -140,96 +144,132 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven // * 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, getter.Getter, error) { + u, r, _, err := c.ResolveChartVersionAndGetRepo(ref, version) + if r != nil { + return u, r.Client, err + } + return u, nil, err +} + +// Same as the ResolveChartVersion method, but returns the chart repositoryy. +func (c *ChartDownloader) ResolveChartVersionAndGetRepo(ref, version string) (*url.URL, *repo.ChartRepository, *getter.HttpGetter, error) { u, err := url.Parse(ref) if err != nil { - return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref) + return nil, nil, nil, fmt.Errorf("invalid chart URL format: %s", ref) } rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile()) if err != nil { - return u, nil, err + return u, nil, nil, err } + g, err := getter.NewHTTPGetter(ref, "", "", "") + if err != nil { + return u, nil, nil, err + } + g.SetCredentials(c.getRepoCredentials(nil)) + if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { // 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 { // If there is no special config, return the default HTTP client and // swallow the error. if err == ErrNoOwnerRepo { - getterConstructor, err := c.Getters.ByScheme(u.Scheme) - if err != nil { - return u, nil, err - } - getter, err := getterConstructor(ref, "", "", "") - return u, getter, err + r := &repo.ChartRepository{} + r.Client = g + g.SetCredentials(c.getRepoCredentials(r)) + return u, r, g, err } - return u, nil, err + return u, nil, nil, err } r, err := repo.NewChartRepository(rc, c.Getters) // 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.Client, err + return u, r, g, 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) + return u, 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 u, nil, err + return u, nil, nil, err } r, err := repo.NewChartRepository(rc, c.Getters) if err != nil { - return u, nil, err + return u, nil, 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 u, r.Client, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) + return u, r, g, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) } cv, err := i.Get(chartName, version) if err != nil { - return u, r.Client, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) + return u, r, g, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) } if len(cv.URLs) == 0 { - return u, r.Client, fmt.Errorf("chart %q has no downloadable URLs", ref) + return u, r, g, 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 u, r.Client, fmt.Errorf("invalid chart URL format: %s", ref) + return u, r, g, fmt.Errorf("invalid chart URL format: %s", ref) } // If the URL is relative (no scheme), prepend the chart repo's base URL if !u.IsAbs() { repoURL, err := url.Parse(rc.URL) if err != nil { - return repoURL, r.Client, err + return repoURL, r, nil, err } q := repoURL.Query() // We need a trailing slash for ResolveReference to work, but make sure there isn't already one repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" u = repoURL.ResolveReference(u) u.RawQuery = q.Encode() - return u, r.Client, err + g, err := getter.NewHTTPGetter(rc.URL, "", "", "") + if err != nil { + return repoURL, r, nil, err + } + g.SetCredentials(c.getRepoCredentials(r)) + return u, r, g, err } - return u, r.Client, nil + return u, r, g, nil +} + +// If this ChartDownloader is not configured to use credentials, and the chart repository sent as an argument is, +// then the repository's configured credentials are returned. +// Else, this ChartDownloader's credentials are returned. +func (c *ChartDownloader) getRepoCredentials(r *repo.ChartRepository) (username, password string) { + username = c.Username + password = c.Password + if r != nil && r.Config != nil { + if username == "" { + username = r.Config.Username + } + if password == "" { + password = r.Config.Password + } + } + return } // VerifyChart takes a path to a chart archive and a keyring, and verifies the chart. diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 0100772e9..80efa77e8 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -99,11 +99,11 @@ func TestDownload(t *testing.T) { t.Fatal("No http provider found") } - getter, err := provider.New(srv.URL, "", "", "") + g, err := provider.New(srv.URL, "", "", "") if err != nil { t.Fatal(err) } - got, err := getter.Get(srv.URL) + got, err := g.Get(srv.URL) if err != nil { t.Fatal(err) } @@ -115,16 +115,21 @@ func TestDownload(t *testing.T) { // test with server backed by basic auth basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok || username != "username" && password != "password" { + if !ok || username != "username" || password != "password" { t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) } fmt.Fprint(w, expect) })) + defer basicAuthSrv.Close() u, _ := url.ParseRequestURI(basicAuthSrv.URL) - u.User = url.UserPassword("username", "password") - got, err = getter.Get(u.String()) + httpgetter, err := getter.NewHTTPGetter(u.String(), "", "", "") + if err != nil { + t.Fatal(err) + } + httpgetter.SetCredentials("username", "password") + got, err = httpgetter.Get(u.String()) if err != nil { t.Fatal(err) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 6d4103bae..89a839b54 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -197,14 +197,6 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { return err } - dl := ChartDownloader{ - Out: m.Out, - Verify: m.Verify, - Keyring: m.Keyring, - HelmHome: m.HelmHome, - Getters: m.Getters, - } - destPath := filepath.Join(m.ChartPath, "charts") tmpPath := filepath.Join(m.ChartPath, "tmpcharts") @@ -245,12 +237,22 @@ 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 } + dl := ChartDownloader{ + Out: m.Out, + Verify: m.Verify, + Keyring: m.Keyring, + HelmHome: m.HelmHome, + Getters: m.Getters, + Username: username, + Password: password, + } + if _, _, err := dl.DownloadTo(churl, "", destPath); err != nil { saveError = fmt.Errorf("could not download %s: %s", churl, err) break @@ -476,22 +478,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/httpgetter.go b/pkg/getter/httpgetter.go index c86b96458..dd462ce5f 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -28,12 +28,23 @@ import ( ) //httpGetter is the efault HTTP(/S) backend handler -type httpGetter struct { - client *http.Client +type HttpGetter struct { + client *http.Client + username string + password string +} + +func (g *HttpGetter) SetCredentials(username, password string) { + g.username = username + g.password = password } //Get performs a Get from repo.Getter and returns the body. -func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { +func (g *HttpGetter) Get(href string) (*bytes.Buffer, error) { + return g.get(href) +} + +func (g *HttpGetter) get(href string) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) // Set a helm specific user agent so that a repo server and metrics can @@ -44,6 +55,10 @@ func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { } req.Header.Set("User-Agent", "Helm/"+strings.TrimPrefix(version.GetVersion(), "v")) + if g.username != "" && g.password != "" { + req.SetBasicAuth(g.username, g.password) + } + resp, err := g.client.Do(req) if err != nil { return buf, err @@ -59,17 +74,22 @@ func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { // newHTTPGetter constructs a valid http/https client as Getter func newHTTPGetter(URL, CertFile, KeyFile, CAFile string) (Getter, error) { - var client httpGetter + return NewHTTPGetter(URL, CertFile, KeyFile, CAFile) +} + +// NewHTTPGetter constructs a valid http/https client as HttpGetter +func NewHTTPGetter(URL, CertFile, KeyFile, CAFile string) (*HttpGetter, error) { + var client HttpGetter if CertFile != "" && KeyFile != "" { tlsConf, err := tlsutil.NewClientTLS(CertFile, KeyFile, CAFile) if err != nil { - return nil, fmt.Errorf("can't create TLS config for client: %s", err.Error()) + return &client, fmt.Errorf("can't create TLS config for client: %s", err.Error()) } tlsConf.BuildNameToCertificate() sni, err := urlutil.ExtractHostname(URL) if err != nil { - return nil, err + return &client, err } tlsConf.ServerName = sni diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 477e0bc4f..fe3fde22a 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -27,7 +27,7 @@ func TestHTTPGetter(t *testing.T) { t.Fatal(err) } - if hg, ok := g.(*httpGetter); !ok { + if hg, ok := g.(*HttpGetter); !ok { t.Fatal("Expected newHTTPGetter to produce an httpGetter") } else if hg.client != http.DefaultClient { t.Fatal("Expected newHTTPGetter to return a default HTTP client.") @@ -42,7 +42,7 @@ func TestHTTPGetter(t *testing.T) { t.Fatal(err) } - if _, ok := g.(*httpGetter); !ok { + if _, ok := g.(*HttpGetter); !ok { t.Fatal("Expected newHTTPGetter to produce an httpGetter") } } diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index b95a7ae07..c76cc7913 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -36,6 +36,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 +119,12 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" indexURL = parsedURL.String() - resp, err := r.Client.Get(indexURL) + g, err := getter.NewHTTPGetter(indexURL, r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile) + if err != nil { + return err + } + g.SetCredentials(r.Config.Username, r.Config.Password) + resp, err := g.Get(indexURL) if err != nil { return err } @@ -186,6 +193,13 @@ 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) { + return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters) +} + +// FindChartInRepoURL finds chart in chart repository pointed by repoURL +// without adding repo to repositories. +// Unlike the FindChartInRepoURL function, this function also receives credentials for the chart repository. +func FindChartInAuthRepoURL(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 +210,8 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF c := Entry{ URL: repoURL, + Username: username, + Password: password, CertFile: certFile, KeyFile: keyFile, CAFile: caFile,