Commit b6335b7d authored by Eyal Ben Moshe's avatar Eyal Ben Moshe Committed by Matthew Fisher

Authentication support for remote charts repositories (#3206)

Authentication support for remote charts repositories.
parent 19c73207
......@@ -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
}
......
......@@ -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 {
......
......@@ -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
......
......@@ -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,
......
......@@ -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")
}
}
......@@ -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)
}
......
......@@ -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
}
......
......@@ -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
......
......@@ -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
......@@ -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
```
......
......@@ -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
```
......
......@@ -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
```
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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.
......
......@@ -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)
}
......
......@@ -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.
......
......@@ -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) {
......
......@@ -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
......
......@@ -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")
}
}
......@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment