Repository: helm/helm
Pull request #6488 (2019-09-24 17:05:20)

fix(downloader): bypass index cache when repository URL defined
port of #6258 

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>


bacongobbler: Yeah, in the current architecture, in order to do a cache lookup we need to know the name of the repository. That's probably why we needed `helm repo add`... That way, `helm dep up` could do a cache lookup.

I haven't put any thought into improving upon what's already in Helm 2. That does seem like a nice enhancement, but I agree that it could be done in a later refactor. Should we move this ahead as-is or do we need to block this until that functionality is complete?
hickeyma: @bacongobbler I noticed something with cache during migration. The [2to3](https://github.com/helm/helm-2to3) plugin does not copy over the cache. This is a conscious decision as cache should be recreated by Helm 3 rather than copied. The repository file when copied initially, will contain references to Helm v2 cache. I would have expected when `helm repo update` command is run that this cache would be regenerated for the Helm v3 configuration path.  Should it regenerate on an update?

Patch:
diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go
index c902b0134..126224048 100644
--- a/internal/resolver/resolver.go
+++ b/internal/resolver/resolver.go
@@ -71,11 +71,20 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
 			return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
 		}
 
-		idx := filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoNames[d.Name]))
+		repoName := repoNames[d.Name]
+		// if the repository was not defined, but the dependency defines a repository url, bypass the cache
+		if repoName == "" && d.Repository != "" {
+			locked[i] = &chart.Dependency{
+				Name:       d.Name,
+				Repository: d.Repository,
+				Version:    d.Version,
+			}
+			continue
+		}
 
-		repoIndex, err := repo.LoadIndexFile(idx)
+		repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
 		if err != nil {
-			return nil, errors.Wrapf(err, "no cached repo found. (try 'helm repo update') %s", idx)
+			return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
 		}
 
 		vs, ok := repoIndex.Entries[d.Name]
diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go
index 4b49a7911..7e10e2d63 100644
--- a/pkg/downloader/manager.go
+++ b/pkg/downloader/manager.go
@@ -220,7 +220,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
 
 		// Any failure to resolve/download a chart should fail:
 		// https://github.com/helm/helm/issues/1439
-		churl, username, password, err := findChartURL(dep.Name, dep.Version, dep.Repository, repos)
+		churl, username, password, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos)
 		if err != nil {
 			saveError = errors.Wrapf(err, "could not find %s", churl)
 			break
@@ -389,7 +389,14 @@ func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, err
 			}
 		}
 		if !found {
-			missing = append(missing, dd.Repository)
+			repository := dd.Repository
+			// Add if URL
+			_, err := url.ParseRequestURI(repository)
+			if err == nil {
+				reposMap[repository] = repository
+				continue
+			}
+			missing = append(missing, repository)
 		}
 	}
 	if len(missing) > 0 {
@@ -460,7 +467,7 @@ 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) (url, username, password string, err error) {
+func (m *Manager) 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) {
 			var entry repo.ChartVersions
@@ -482,6 +489,10 @@ func findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRep
 			return
 		}
 	}
+	url, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", m.Getters)
+	if err == nil {
+		return
+	}
 	err = errors.Errorf("chart %s not found in %s", name, repoURL)
 	return
 }


Test patch:
diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go
index cf9ad7ba4..53d67ef59 100644
--- a/cmd/helm/dependency_update_test.go
+++ b/cmd/helm/dependency_update_test.go
@@ -117,33 +117,6 @@ func TestDependencyUpdateCmd(t *testing.T) {
 	}
 }
 
-func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
-	defer resetEnv()()
-	defer ensure.HelmHome(t)()
-
-	srv := repotest.NewServer(helmpath.ConfigPath())
-	defer srv.Stop()
-	copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Logf("Copied charts:\n%s", strings.Join(copied, "\n"))
-	t.Logf("Listening on directory %s", srv.Root())
-
-	chartname := "depup"
-	createTestingChart(t, helmpath.DataPath(), chartname, srv.URL())
-
-	_, out, err := executeActionCommand(fmt.Sprintf("dependency update --skip-refresh %s", helmpath.DataPath(chartname)))
-	if err == nil {
-		t.Fatal("Expected failure to find the repo with skipRefresh")
-	}
-
-	// This is written directly to stdout, so we have to capture as is.
-	if strings.Contains(out, `update from the "test" chart repository`) {
-		t.Errorf("Repo was unexpectedly updated\n%s", out)
-	}
-}
-
 func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
 	defer resetEnv()()
 	defer ensure.HelmHome(t)()
diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go
index 3c402aaea..43c527dbf 100644
--- a/internal/resolver/resolver_test.go
+++ b/internal/resolver/resolver_test.go
@@ -40,7 +40,11 @@ func TestResolve(t *testing.T) {
 			req: []*chart.Dependency{
 				{Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"},
 			},
-			err: true,
+			expect: &chart.Lock{
+				Dependencies: []*chart.Dependency{
+					{Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"},
+				},
+			},
 		},
 		{
 			name: "chart not found failure",
@@ -90,39 +94,41 @@ func TestResolve(t *testing.T) {
 	repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}
 	r := New("testdata/chartpath", "testdata/repository")
 	for _, tt := range tests {
-		l, err := r.Resolve(tt.req, repoNames)
-		if err != nil {
-			if tt.err {
-				continue
+		t.Run(tt.name, func(t *testing.T) {
+			l, err := r.Resolve(tt.req, repoNames)
+			if err != nil {
+				if tt.err {
+					return
+				}
+				t.Fatal(err)
 			}
-			t.Fatal(err)
-		}
 
-		if tt.err {
-			t.Fatalf("Expected error in test %q", tt.name)
-		}
+			if tt.err {
+				t.Fatalf("Expected error in test %q", tt.name)
+			}
 
-		if h, err := HashReq(tt.expect.Dependencies); err != nil {
-			t.Fatal(err)
-		} else if h != l.Digest {
-			t.Errorf("%q: hashes don't match.", tt.name)
-		}
+			if h, err := HashReq(tt.expect.Dependencies); err != nil {
+				t.Fatal(err)
+			} else if h != l.Digest {
+				t.Errorf("%q: hashes don't match.", tt.name)
+			}
 
-		// Check fields.
-		if len(l.Dependencies) != len(tt.req) {
-			t.Errorf("%s: wrong number of dependencies in lock", tt.name)
-		}
-		d0 := l.Dependencies[0]
-		e0 := tt.expect.Dependencies[0]
-		if d0.Name != e0.Name {
-			t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name)
-		}
-		if d0.Repository != e0.Repository {
-			t.Errorf("%s: expected repo %s, got %s", tt.name, e0.Repository, d0.Repository)
-		}
-		if d0.Version != e0.Version {
-			t.Errorf("%s: expected version %s, got %s", tt.name, e0.Version, d0.Version)
-		}
+			// Check fields.
+			if len(l.Dependencies) != len(tt.req) {
+				t.Errorf("%s: wrong number of dependencies in lock", tt.name)
+			}
+			d0 := l.Dependencies[0]
+			e0 := tt.expect.Dependencies[0]
+			if d0.Name != e0.Name {
+				t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name)
+			}
+			if d0.Repository != e0.Repository {
+				t.Errorf("%s: expected repo %s, got %s", tt.name, e0.Repository, d0.Repository)
+			}
+			if d0.Version != e0.Version {
+				t.Errorf("%s: expected version %s, got %s", tt.name, e0.Version, d0.Version)
+			}
+		})
 	}
 }
 
diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go
index 06c62ecaa..8a8b9084d 100644
--- a/pkg/downloader/manager_test.go
+++ b/pkg/downloader/manager_test.go
@@ -77,7 +77,7 @@ func TestFindChartURL(t *testing.T) {
 	version := "0.1.0"
 	repoURL := "http://example.com/charts"
 
-	churl, username, password, err := findChartURL(name, version, repoURL, repos)
+	churl, username, password, err := m.findChartURL(name, version, repoURL, repos)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -106,11 +106,11 @@ func TestGetRepoNames(t *testing.T) {
 		err    bool
 	}{
 		{
-			name: "no repo definition failure",
+			name: "no repo definition, but references a url",
 			req: []*chart.Dependency{
 				{Name: "oedipus-rex", Repository: "http://example.com/test"},
 			},
-			err: true,
+			expect: map[string]string{"http://example.com/test": "http://example.com/test"},
 		},
 		{
 			name: "no repo definition failure -- stable repo",