Repository: helm/helm (go)

Issue:
Helm3 should not decode URLs
Related helm v2 issue - #6048 

Output of `helm version`:
```
version.BuildInfo{Version:"v3.0+unreleased", GitCommit:"cc450563835088945aa79dbfa0bce0bc031fb390", GitTreeState:"clean", GoVersion:"go1.13.1"}
```

```
$ helm3 repo add blah http://localhost:8080/blah%2Fpath/blah
Error: looks like "http://localhost:8080/blah%2Fpath/blah" is not a valid chart repository or cannot be reached: Get http://localhost:8080/blah/path/blah/index.yaml: dial tcp [::1]:8080: connect: connection refused
```

Pull request:
fix repo url being decoded while downloading repo index
**What this PR does / why we need it**:
Port of #6060 (v2 fix)

**If applicable**:
- [ ] this PR contains documentation
- [X] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


Patch:
diff --git a/cmd/helm/install.go b/cmd/helm/install.go
index 2540558a5..d43a12f80 100644
--- a/cmd/helm/install.go
+++ b/cmd/helm/install.go
@@ -74,7 +74,8 @@ set for a key called 'foo', the 'newbar' value would take precedence:
 
 
 To check the generated manifests of a release without installing the chart,
-the '--debug' and '--dry-run' flags can be combined.
+the '--debug' and '--dry-run' flags can be combined. This will still require a
+round-trip to the Tiller server.
 
 If --verify is set, the chart MUST have a provenance file, and the provenance
 file MUST pass all verification steps.
diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go
index 1d90142c1..8cf4991e0 100644
--- a/pkg/chartutil/save.go
+++ b/pkg/chartutil/save.go
@@ -93,7 +93,7 @@ func Save(c *chart.Chart, outDir string) (string, error) {
 	filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
 	filename = filepath.Join(outDir, filename)
 	if stat, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
-		if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
+		if err := os.MkdirAll(filepath.Dir(filename), 0755); !os.IsExist(err) {
 			return "", err
 		}
 	} else if !stat.IsDir() {
diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go
index a88f68527..c8d0d6a3d 100644
--- a/pkg/repo/chartrepo.go
+++ b/pkg/repo/chartrepo.go
@@ -23,6 +23,7 @@ import (
 	"io/ioutil"
 	"net/url"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
 
@@ -109,14 +110,14 @@ func (r *ChartRepository) Load() error {
 
 // DownloadIndexFile fetches the index from a repository.
 func (r *ChartRepository) DownloadIndexFile() (string, error) {
-	var indexURL string
 	parsedURL, err := url.Parse(r.Config.URL)
 	if err != nil {
 		return "", err
 	}
-	parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml"
+	parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
+	parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
 
-	indexURL = parsedURL.String()
+	indexURL := parsedURL.String()
 	// TODO add user-agent
 	resp, err := r.Client.Get(indexURL,
 		getter.WithURL(r.Config.URL),


Test patch:
diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go
index 377e89dcb..fb608e5cf 100644
--- a/pkg/chartutil/save_test.go
+++ b/pkg/chartutil/save_test.go
@@ -19,7 +19,6 @@ package chartutil
 import (
 	"io/ioutil"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -35,41 +34,37 @@ func TestSave(t *testing.T) {
 	}
 	defer os.RemoveAll(tmp)
 
-	for _, dest := range []string{tmp, path.Join(tmp, "newdir")} {
-		t.Run("outDir="+dest, func(t *testing.T) {
-			c := &chart.Chart{
-				Metadata: &chart.Metadata{
-					APIVersion: chart.APIVersionV1,
-					Name:       "ahab",
-					Version:    "1.2.3",
-				},
-				Files: []*chart.File{
-					{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
-				},
-			}
-
-			where, err := Save(c, dest)
-			if err != nil {
-				t.Fatalf("Failed to save: %s", err)
-			}
-			if !strings.HasPrefix(where, dest) {
-				t.Fatalf("Expected %q to start with %q", where, dest)
-			}
-			if !strings.HasSuffix(where, ".tgz") {
-				t.Fatalf("Expected %q to end with .tgz", where)
-			}
-
-			c2, err := loader.LoadFile(where)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if c2.Name() != c.Name() {
-				t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
-			}
-			if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
-				t.Fatal("Files data did not match")
-			}
-		})
+	c := &chart.Chart{
+		Metadata: &chart.Metadata{
+			APIVersion: chart.APIVersionV1,
+			Name:       "ahab",
+			Version:    "1.2.3",
+		},
+		Files: []*chart.File{
+			{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
+		},
+	}
+
+	where, err := Save(c, tmp)
+	if err != nil {
+		t.Fatalf("Failed to save: %s", err)
+	}
+	if !strings.HasPrefix(where, tmp) {
+		t.Fatalf("Expected %q to start with %q", where, tmp)
+	}
+	if !strings.HasSuffix(where, ".tgz") {
+		t.Fatalf("Expected %q to end with .tgz", where)
+	}
+
+	c2, err := loader.LoadFile(where)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if c2.Name() != c.Name() {
+		t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
+	}
+	if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
+		t.Fatal("Files data did not match")
 	}
 }
 
diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go
index f5203646c..d0596c219 100644
--- a/pkg/repo/index_test.go
+++ b/pkg/repo/index_test.go
@@ -18,12 +18,14 @@ package repo
 
 import (
 	"io/ioutil"
+	"net/http"
 	"os"
 	"testing"
 
-	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/cli"
 	"helm.sh/helm/v3/pkg/getter"
+
+	"helm.sh/helm/v3/pkg/chart"
 )
 
 const (
@@ -128,39 +130,87 @@ func TestMerge(t *testing.T) {
 }
 
 func TestDownloadIndexFile(t *testing.T) {
-	srv, err := startLocalServerForTests(nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer srv.Close()
+	t.Run("should  download index file", func(t *testing.T) {
+		srv, err := startLocalServerForTests(nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		defer srv.Close()
+
+		r, err := NewChartRepository(&Entry{
+			Name: testRepo,
+			URL:  srv.URL,
+		}, getter.All(&cli.EnvSettings{}))
+		if err != nil {
+			t.Errorf("Problem creating chart repository from %s: %v", testRepo, err)
+		}
 
-	r, err := NewChartRepository(&Entry{
-		Name: testRepo,
-		URL:  srv.URL,
-	}, getter.All(&cli.EnvSettings{}))
-	if err != nil {
-		t.Errorf("Problem creating chart repository from %s: %v", testRepo, err)
-	}
+		idx, err := r.DownloadIndexFile()
+		if err != nil {
+			t.Fatalf("Failed to download index file to %s: %#v", idx, err)
+		}
 
-	idx, err := r.DownloadIndexFile()
-	if err != nil {
-		t.Fatalf("Failed to download index file to %s: %#v", idx, err)
-	}
+		if _, err := os.Stat(idx); err != nil {
+			t.Fatalf("error finding created index file: %#v", err)
+		}
 
-	if _, err := os.Stat(idx); err != nil {
-		t.Fatalf("error finding created index file: %#v", err)
-	}
+		b, err := ioutil.ReadFile(idx)
+		if err != nil {
+			t.Fatalf("error reading index file: %#v", err)
+		}
 
-	b, err := ioutil.ReadFile(idx)
-	if err != nil {
-		t.Fatalf("error reading index file: %#v", err)
-	}
+		i, err := loadIndex(b)
+		if err != nil {
+			t.Fatalf("Index %q failed to parse: %s", testfile, err)
+		}
+		verifyLocalIndex(t, i)
+	})
+
+	t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) {
+		chartRepoURLPath := "/some%2Fpath/test"
+		fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml")
+		if err != nil {
+			t.Fatal(err)
+		}
+		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			if r.URL.RawPath == chartRepoURLPath+"/index.yaml" {
+				w.Write(fileBytes)
+			}
+		})
+		srv, err := startLocalServerForTests(handler)
+		if err != nil {
+			t.Fatal(err)
+		}
+		defer srv.Close()
+
+		r, err := NewChartRepository(&Entry{
+			Name: testRepo,
+			URL:  srv.URL + chartRepoURLPath,
+		}, getter.All(&cli.EnvSettings{}))
+		if err != nil {
+			t.Errorf("Problem creating chart repository from %s: %v", testRepo, err)
+		}
 
-	i, err := loadIndex(b)
-	if err != nil {
-		t.Fatalf("Index %q failed to parse: %s", testfile, err)
-	}
-	verifyLocalIndex(t, i)
+		idx, err := r.DownloadIndexFile()
+		if err != nil {
+			t.Fatalf("Failed to download index file to %s: %#v", idx, err)
+		}
+
+		if _, err := os.Stat(idx); err != nil {
+			t.Fatalf("error finding created index file: %#v", err)
+		}
+
+		b, err := ioutil.ReadFile(idx)
+		if err != nil {
+			t.Fatalf("error reading index file: %#v", err)
+		}
+
+		i, err := loadIndex(b)
+		if err != nil {
+			t.Fatalf("Index %q failed to parse: %s", testfile, err)
+		}
+		verifyLocalIndex(t, i)
+	})
 }
 
 func verifyLocalIndex(t *testing.T, i *IndexFile) {