Repository: argoproj/argo-cd
Pull request #5646 (2021-03-01 20:54:47)

feat: made Helm v3 the default and removed version auto-detection
Note on DCO:

If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.

Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [x] The title of the PR states what changed and the related issues number (used for the release note).
* [x] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [x] Does this PR require documentation updates?
* [x] I've updated documentation as required by this PR.
* [ ] Optional. My organization is added to USERS.md.
* [x] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/tree/master/community#contributing-to-argo)
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)). 



codecov-io: # [Codecov](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=h1) Report
> Merging [#5646](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=desc) (74e3027) into [master](https://codecov.io/gh/argoproj/argo-cd/commit/7ec8b266f865ae996c786629662618bd5fa43095?el=desc) (7ec8b26) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/argoproj/argo-cd/pull/5646/graphs/tree.svg?width=650&height=150&src=pr&token=kf5kwuegwE)](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #5646      +/-   ##
==========================================
+ Coverage   40.91%   40.92%   +0.01%     
==========================================
  Files         144      144              
  Lines       19210    19222      +12     
==========================================
+ Hits         7860     7867       +7     
- Misses      10255    10259       +4     
- Partials     1095     1096       +1     
```


| [Impacted Files](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [util/helm/helmver.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-dXRpbC9oZWxtL2hlbG12ZXIuZ28=) | `80.00% <ø> (+5.00%)` | :arrow_up: |
| [util/helm/cmd.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-dXRpbC9oZWxtL2NtZC5nbw==) | `29.11% <100.00%> (-1.76%)` | :arrow_down: |
| [server/application/logs.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-c2VydmVyL2FwcGxpY2F0aW9uL2xvZ3MuZ28=) | `85.71% <0.00%> (-2.53%)` | :arrow_down: |
| [server/application/application.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-c2VydmVyL2FwcGxpY2F0aW9uL2FwcGxpY2F0aW9uLmdv) | `28.81% <0.00%> (-0.07%)` | :arrow_down: |
| [cmd/argocd/commands/login.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-Y21kL2FyZ29jZC9jb21tYW5kcy9sb2dpbi5nbw==) | `2.42% <0.00%> (ø)` | |
| [pkg/apis/application/v1alpha1/types.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-cGtnL2FwaXMvYXBwbGljYXRpb24vdjFhbHBoYTEvdHlwZXMuZ28=) | `56.49% <0.00%> (+0.19%)` | :arrow_up: |
| [cmd/util/app.go](https://codecov.io/gh/argoproj/argo-cd/pull/5646/diff?src=pr&el=tree#diff-Y21kL3V0aWwvYXBwLmdv) | `36.28% <0.00%> (+2.84%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=footer). Last update [7ec8b26...74e3027](https://codecov.io/gh/argoproj/argo-cd/pull/5646?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jannfis: @ishitasequeira Please rebase this PR to latest master, that will fix the CodeQL complaint. Thanks!

Patch:
diff --git a/USERS.md b/USERS.md
index 84fcfe7f9..e316cda6d 100644
--- a/USERS.md
+++ b/USERS.md
@@ -8,6 +8,7 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [3Rein](https://www.3rein.com/)
 1. [7shifts](https://www.7shifts.com/)
 1. [Adevinta](https://www.adevinta.com/)
+1. [Ambassador Labs](https://www.getambassador.io/)
 1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
 1. [AppDirect](https://www.appdirect.com)
 1. [Arctiq Inc.](https://www.arctiq.ca)
@@ -117,4 +118,5 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [WeMo Scooter](https://www.wemoscooter.com/)
 1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli
 1. [Yieldlab](https://www.yieldlab.de/)
-1. [Sap Labs] (http://sap.com)
+1. [Sap Labs](http://sap.com)
+1. [Smilee.io](https://smilee.io)
diff --git a/common/common.go b/common/common.go
index 563cda98f..d411c013c 100644
--- a/common/common.go
+++ b/common/common.go
@@ -188,6 +188,8 @@ const (
 	EnvEnableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
 	// EnvGithubAppCredsExpirationDuration controls the caching of Github app credentials. This value is in minutes (default: 60)
 	EnvGithubAppCredsExpirationDuration = "ARGOCD_GITHUB_APP_CREDS_EXPIRATION_DURATION"
+	// EnvHelmIndexCacheDuration controls how the helm repository index file is cached for (default: 0)
+	EnvHelmIndexCacheDuration = "ARGOCD_HELM_INDEX_CACHE_DURATION"
 )
 
 const (
diff --git a/docs/operator-manual/troubleshooting.md b/docs/operator-manual/troubleshooting.md
index e55c1ffab..5d642e075 100644
--- a/docs/operator-manual/troubleshooting.md
+++ b/docs/operator-manual/troubleshooting.md
@@ -8,7 +8,7 @@ connectivity issues.
 Argo CD provides multiple ways to customize system behavior and has a lot of settings. It might be dangerous to modify
 settings on Argo CD used in production by multiple users. Before applying settings you can use `argocd-util` binary to
 make sure that settings are valid and Argo CD is working as expected. The `argocd-util` binary is available in `argocd`
-image and might be used using docker. 
+image and might be used using docker.
 You can download the latest `argocd-util` binary from [the latest release page of this repository](https://github.com/argoproj/argo-cd/releases/latest), which will include the `argocd-util` CLI.
 Example:
 
@@ -21,7 +21,7 @@ If you are using Linux you can extract `argocd-util` binary from docker image:
 
 ```bash
 docker run --rm -it -w /src -v $(pwd):/src argocd cp /usr/local/bin/argocd-util ./argocd-util
-``` 
+```
 
 The `argocd-util settings validate` command performs basic settings validation and print short summary
 of each settings group.
@@ -38,10 +38,11 @@ docker run --rm -it -w /src -v $(pwd):/src argoproj/argocd:<version> \
   argocd-util settings resource-overrides ignore-differences ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml
 ```
 
-* Health Assessment
+**Health Assessment**
 
-[Health assessment](../user-guide/diffing.md) allows excluding some resource fields from diffing process.
-The diffing customizations are configured in `resource.customizations` field of `argocd-cm` ConfigMap. 
+Argo CD provides built-in [health assessment](./health.md) for several kubernetes resources which can be further
+customized by writing your own health checks in [Lua](https://www.lua.org/).
+The health checks are configured in the `resource.customizations` field of `argocd-cm` ConfigMap.
 
 The following `argocd-util` command assess resource health using Lua script configured in the specified ConfigMap.
 
@@ -50,7 +51,7 @@ docker run --rm -it -w /src -v $(pwd):/src argoproj/argocd:<version> \
   argocd-util settings resource-overrides health ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml
 ```
 
-* Resource Actions
+**Resource Actions**
 
 Resource actions allows configuring named Lua script which performs resource modification.
 
@@ -59,14 +60,14 @@ applied modifications.
 
 ```bash
 docker run --rm -it -w /src -v $(pwd):/src argoproj/argocd:<version> \
-  argocd-util settings resource-overrides run-action /tmp/deploy.yaml restart --argocd-cm-path /private/tmp/argocd-cm.yaml 
+  argocd-util settings resource-overrides run-action /tmp/deploy.yaml restart --argocd-cm-path /private/tmp/argocd-cm.yaml
 ```
 
 The following `argocd-util` command lists actions available for a given resource using Lua script configured in the specified ConfigMap.
 
 ```bash
 docker run --rm -it -w /src -v $(pwd):/src argoproj/argocd:<version> \
-  argocd-util settings resource-overrides list-actions /tmp/deploy.yaml --argocd-cm-path /private/tmp/argocd-cm.yaml 
+  argocd-util settings resource-overrides list-actions /tmp/deploy.yaml --argocd-cm-path /private/tmp/argocd-cm.yaml
 ```
 
 ## Cluster credentials
@@ -81,7 +82,7 @@ kubectl exec -n argocd -it \
   $(kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-application-controller -o jsonpath='{.items[0].metadata.name}') bash
 ```
 
-2 Use `argocd-util kubeconfig` command to export kubeconfig file from the configured Secret: 
+2 Use `argocd-util kubeconfig` command to export kubeconfig file from the configured Secret:
 
 ```
 argocd-util kubeconfig https://<api-server-url> /tmp/kubeconfig --namespace argocd
@@ -92,4 +93,4 @@ argocd-util kubeconfig https://<api-server-url> /tmp/kubeconfig --namespace argo
 ```
 export KUBECONFIG=/tmp/kubeconfig
 kubectl get pods -v 9
-``` 
\ No newline at end of file
+```
\ No newline at end of file
diff --git a/docs/user-guide/helm.md b/docs/user-guide/helm.md
index 75661ab36..ff3abe989 100644
--- a/docs/user-guide/helm.md
+++ b/docs/user-guide/helm.md
@@ -178,12 +178,12 @@ After that you have to use your custom image for ArgoCD installation.
 
 ## Helm Version
 
-ArgoCD normally detects which version of Helm to use by looking at the `apiVersion` in Chart.yaml.
+Argo CD will assume that the Helm chart is v3 (even if the apiVersion field in the chart is Helm v2), unless v2 is explicitly specified within the Argo CD Application (see below).
 
 If needed, it is possible to specifically set the Helm version to template with by setting the `helm-version` flag on the cli (either v2 or v3):
 
 ```bash
-argocd app set helm-guestbook --helm-version v2
+argocd app set helm-guestbook --helm-version v3
 ```
 
 Or using declarative syntax:
@@ -192,5 +192,5 @@ Or using declarative syntax:
 spec:
   source:
     helm:
-      version: v2
+      version: v3
 ```
diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go
index ad3312458..512d30a9d 100644
--- a/reposerver/repository/repository.go
+++ b/reposerver/repository/repository.go
@@ -202,7 +202,7 @@ func (s *Service) runRepoOperation(
 	var err error
 	revision = textutils.FirstNonEmpty(revision, source.TargetRevision)
 	if source.IsHelm() {
-		helmClient, revision, err = s.newHelmClientResolveRevision(repo, revision, source.Chart)
+		helmClient, revision, err = s.newHelmClientResolveRevision(repo, revision, source.Chart, settings.noCache)
 		if err != nil {
 			return nil, err
 		}
@@ -1299,7 +1299,7 @@ func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision s
 	return gitClient, commitSHA, nil
 }
 
-func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revision string, chart string) (helm.Client, string, error) {
+func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revision string, chart string, noCache bool) (helm.Client, string, error) {
 	helmClient := s.newHelmClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI || helm.IsHelmOciChart(chart))
 	if helm.IsVersion(revision) {
 		return helmClient, revision, nil
@@ -1311,7 +1311,7 @@ func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revisi
 	if err != nil {
 		return nil, "", fmt.Errorf("invalid revision '%s': %v", revision, err)
 	}
-	index, err := helmClient.GetIndex()
+	index, err := helmClient.GetIndex(noCache)
 	if err != nil {
 		return nil, "", err
 	}
@@ -1360,7 +1360,7 @@ func checkoutRevision(gitClient git.Client, revision string) error {
 }
 
 func (s *Service) GetHelmCharts(ctx context.Context, q *apiclient.HelmChartsRequest) (*apiclient.HelmChartsResponse, error) {
-	index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI).GetIndex()
+	index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI).GetIndex(true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/server/application/application.go b/server/application/application.go
index 83b5b33e8..68e1d606d 100644
--- a/server/application/application.go
+++ b/server/application/application.go
@@ -1404,7 +1404,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
 			return ambiguousRevision, ambiguousRevision, nil
 		}
 		client := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI || app.Spec.Source.IsHelmOci())
-		index, err := client.GetIndex()
+		index, err := client.GetIndex(false)
 		if err != nil {
 			return "", "", err
 		}
diff --git a/util/argo/argo.go b/util/argo/argo.go
index f2aac32ae..bcce4104a 100644
--- a/util/argo/argo.go
+++ b/util/argo/argo.go
@@ -161,7 +161,7 @@ func TestRepo(repo *argoappv1.Repository) error {
 				_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI).TestHelmOCI()
 				return err
 			} else {
-				_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI).GetIndex()
+				_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI).GetIndex(false)
 				return err
 			}
 		},
diff --git a/util/env/env.go b/util/env/env.go
index f26ecb087..a43002f7b 100644
--- a/util/env/env.go
+++ b/util/env/env.go
@@ -3,6 +3,9 @@ package env
 import (
 	"os"
 	"strconv"
+	"time"
+
+	timeutil "github.com/argoproj/pkg/time"
 
 	log "github.com/sirupsen/logrus"
 )
@@ -32,3 +35,29 @@ func ParseNumFromEnv(env string, defaultValue, min, max int) int {
 	}
 	return num
 }
+
+// Helper function to parse a time duration from an environment variable. Returns a
+// default if env is not set, is not parseable to a duration, exceeds max (if
+// max is greater than 0) or is less than min.
+func ParseDurationFromEnv(env string, defaultValue, min, max time.Duration) time.Duration {
+	str := os.Getenv(env)
+	if str == "" {
+		return defaultValue
+	}
+	durPtr, err := timeutil.ParseDuration(str)
+	if err != nil {
+		log.Warnf("Could not parse '%s' as a number from environment %s", str, env)
+		return defaultValue
+	}
+
+	dur := *durPtr
+	if dur < min {
+		log.Warnf("Value in %s is %s, which is less than minimum %s allowed", env, dur, min)
+		return defaultValue
+	}
+	if dur > max {
+		log.Warnf("Value in %s is %s, which is greater than maximum %s allowed", env, dur, max)
+		return defaultValue
+	}
+	return dur
+}
diff --git a/util/helm/client.go b/util/helm/client.go
index 3c7ed2cfc..c12163c0a 100644
--- a/util/helm/client.go
+++ b/util/helm/client.go
@@ -16,17 +16,25 @@ import (
 	"strings"
 	"time"
 
+	"github.com/argoproj/argo-cd/common"
+
+	"github.com/argoproj/argo-cd/util/env"
+
 	"github.com/Masterminds/semver"
 	"github.com/argoproj/pkg/sync"
 	log "github.com/sirupsen/logrus"
 	"gopkg.in/yaml.v2"
 
+	"github.com/argoproj/argo-cd/util/cache"
 	executil "github.com/argoproj/argo-cd/util/exec"
 	"github.com/argoproj/argo-cd/util/io"
 )
 
 var (
-	globalLock = sync.NewKeyLock()
+	globalLock    = sync.NewKeyLock()
+	indexDuration = env.ParseDurationFromEnv(common.EnvHelmIndexCacheDuration, 0, 0, time.Hour*24)
+	indexCache    = cache.NewInMemoryCache(indexDuration)
+	indexLock     = sync.NewKeyLock()
 )
 
 type Creds struct {
@@ -41,7 +49,7 @@ type Creds struct {
 type Client interface {
 	CleanChartCache(chart string, version *semver.Version) error
 	ExtractChart(chart string, version *semver.Version) (string, io.Closer, error)
-	GetIndex() (*Index, error)
+	GetIndex(noCache bool) (*Index, error)
 	TestHelmOCI() (bool, error)
 }
 
@@ -202,22 +210,38 @@ func (c *nativeHelmChart) ExtractChart(chart string, version *semver.Version) (s
 	}), nil
 }
 
-func (c *nativeHelmChart) GetIndex() (*Index, error) {
-	start := time.Now()
+func (c *nativeHelmChart) GetIndex(noCache bool) (*Index, error) {
+	indexLock.Lock(c.repoURL)
+	defer indexLock.Unlock(c.repoURL)
 
-	data, err := c.loadRepoIndex()
-	if err != nil {
-		return nil, err
+	var data []byte
+	useCache := !noCache && indexDuration > 0
+	if useCache {
+		if err := indexCache.Get(c.repoURL, &data); err != nil && err != cache.ErrCacheMiss {
+			log.Warnf("Failed to load index cache for repo: %s: %v", c.repoURL, err)
+		}
+	}
+
+	if len(data) == 0 {
+		start := time.Now()
+		var err error
+		data, err = c.loadRepoIndex()
+		if err != nil {
+			return nil, err
+		}
+		log.WithFields(log.Fields{"seconds": time.Since(start).Seconds()}).Info("took to get index")
+
+		if err := indexCache.Set(&cache.Item{Key: c.repoURL, Object: data}); err != nil {
+			log.Warnf("Failed to store index cache for repo: %s: %v", c.repoURL, err)
+		}
 	}
 
 	index := &Index{}
-	err = yaml.NewDecoder(bytes.NewBuffer(data)).Decode(index)
+	err := yaml.NewDecoder(bytes.NewBuffer(data)).Decode(index)
 	if err != nil {
 		return nil, err
 	}
 
-	log.WithFields(log.Fields{"seconds": time.Since(start).Seconds()}).Info("took to get index")
-
 	return index, nil
 }
 
diff --git a/util/helm/cmd.go b/util/helm/cmd.go
index ba7c89bb1..17f841e01 100644
--- a/util/helm/cmd.go
+++ b/util/helm/cmd.go
@@ -23,20 +23,15 @@ type Cmd struct {
 }
 
 func NewCmd(workDir string, version string) (*Cmd, error) {
-	if version != "" {
-		switch version {
-		case "v2":
-			return NewCmdWithVersion(workDir, HelmV2, false)
-		case "v3":
-			return NewCmdWithVersion(workDir, HelmV3, false)
-		}
-	}
-	helmVersion, err := getHelmVersion(workDir)
-	if err != nil {
-		return nil, err
-	}
 
-	return NewCmdWithVersion(workDir, *helmVersion, false)
+	switch version {
+	case "v2":
+		return NewCmdWithVersion(workDir, HelmV2, false)
+	// If v3 is specified (or by default, if no value is specified) then use v3
+	case "", "v3":
+		return NewCmdWithVersion(workDir, HelmV3, false)
+	}
+	return nil, fmt.Errorf("helm chart version '%s' is not supported", version)
 }
 
 func NewCmdWithVersion(workDir string, version HelmVer, isHelmOci bool) (*Cmd, error) {
diff --git a/util/helm/helmver.go b/util/helm/helmver.go
index fcbf15d17..fe9648539 100644
--- a/util/helm/helmver.go
+++ b/util/helm/helmver.go
@@ -1,13 +1,8 @@
 package helm
 
 import (
-	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
-
-	"gopkg.in/yaml.v2"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
 var (
@@ -54,27 +49,6 @@ func cleanupChartLockFile(chartPath string) (func(), error) {
 	}, nil
 }
 
-func getHelmVersion(chartPath string) (*HelmVer, error) {
-	data, err := ioutil.ReadFile(path.Join(chartPath, "Chart.yaml"))
-	if err != nil {
-		return nil, err
-	}
-	obj := make(map[string]interface{})
-	err = yaml.Unmarshal(data, &obj)
-	if err != nil {
-		return nil, err
-	}
-	version, _, _ := unstructured.NestedString(obj, "apiVersion")
-	switch version {
-	case "", "v1":
-		return &HelmV2, nil
-	case "v2":
-		return &HelmV3, nil
-
-	}
-	return nil, fmt.Errorf("helm chart version '%s' is not supported", version)
-}
-
 // HelmVer contains Helm version specific settings such as helm binary and command names
 type HelmVer struct {
 	binaryName                  string
diff --git a/util/helm/mocks/Client.go b/util/helm/mocks/Client.go
index 2683c400a..db7a6a5b5 100644
--- a/util/helm/mocks/Client.go
+++ b/util/helm/mocks/Client.go
@@ -4,7 +4,6 @@ package mocks
 
 import (
 	helm "github.com/argoproj/argo-cd/util/helm"
-
 	io "github.com/argoproj/argo-cd/util/io"
 
 	mock "github.com/stretchr/testify/mock"
@@ -61,13 +60,13 @@ func (_m *Client) ExtractChart(chart string, version *semver.Version) (string, i
 	return r0, r1, r2
 }
 
-// GetIndex provides a mock function with given fields:
-func (_m *Client) GetIndex() (*helm.Index, error) {
-	ret := _m.Called()
+// GetIndex provides a mock function with given fields: noCache
+func (_m *Client) GetIndex(noCache bool) (*helm.Index, error) {
+	ret := _m.Called(noCache)
 
 	var r0 *helm.Index
-	if rf, ok := ret.Get(0).(func() *helm.Index); ok {
-		r0 = rf()
+	if rf, ok := ret.Get(0).(func(bool) *helm.Index); ok {
+		r0 = rf(noCache)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(*helm.Index)
@@ -75,8 +74,8 @@ func (_m *Client) GetIndex() (*helm.Index, error) {
 	}
 
 	var r1 error
-	if rf, ok := ret.Get(1).(func() error); ok {
-		r1 = rf()
+	if rf, ok := ret.Get(1).(func(bool) error); ok {
+		r1 = rf(noCache)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -92,9 +91,7 @@ func (_m *Client) TestHelmOCI() (bool, error) {
 	if rf, ok := ret.Get(0).(func() bool); ok {
 		r0 = rf()
 	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(bool)
-		}
+		r0 = ret.Get(0).(bool)
 	}
 
 	var r1 error


Test patch:
diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go
index bd727644f..dcda8c189 100644
--- a/reposerver/repository/repository_test.go
+++ b/reposerver/repository/repository_test.go
@@ -78,7 +78,7 @@ func newServiceWithOpt(cf clientFunc) (*Service, *gitmocks.Client) {
 
 	chart := "my-chart"
 	version := semver.MustParse("1.1.0")
-	helmClient.On("GetIndex").Return(&helm.Index{Entries: map[string]helm.Entries{
+	helmClient.On("GetIndex", true).Return(&helm.Index{Entries: map[string]helm.Entries{
 		chart: {{Version: "1.0.0"}, {Version: version.String()}},
 	}}, nil)
 	helmClient.On("ExtractChart", chart, version).Return("./testdata/my-chart", io.NopCloser, nil)
@@ -1110,12 +1110,12 @@ func TestService_newHelmClientResolveRevision(t *testing.T) {
 	service := newService(".")
 
 	t.Run("EmptyRevision", func(t *testing.T) {
-		_, _, err := service.newHelmClientResolveRevision(&argoappv1.Repository{}, "", "")
+		_, _, err := service.newHelmClientResolveRevision(&argoappv1.Repository{}, "", "", true)
 		assert.EqualError(t, err, "invalid revision '': improper constraint: ")
 	})
 	t.Run("InvalidRevision", func(t *testing.T) {
-		_, _, err := service.newHelmClientResolveRevision(&argoappv1.Repository{}, "???", "")
-		assert.EqualError(t, err, "invalid revision '???': improper constraint: ???")
+		_, _, err := service.newHelmClientResolveRevision(&argoappv1.Repository{}, "???", "", true)
+		assert.EqualError(t, err, "invalid revision '???': improper constraint: ???", true)
 	})
 }
 
diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go
index b3160894a..dde2d34e7 100644
--- a/test/e2e/helm_test.go
+++ b/test/e2e/helm_test.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"net/http"
 	"os"
+	"strings"
 	"testing"
 
 	"github.com/argoproj/gitops-engine/pkg/health"
@@ -322,7 +323,7 @@ func TestKubeVersion(t *testing.T) {
 			kubeVersion := FailOnErr(Run(".", "kubectl", "-n", DeploymentNamespace(), "get", "cm", "my-map",
 				"-o", "jsonpath={.data.kubeVersion}")).(string)
 			// Capabilities.KubeVersion defaults to 1.9.0, we assume here you are running a later version
-			assert.Equal(t, GetVersions().ServerVersion.Format("v%s.%s.0"), kubeVersion)
+			assert.LessOrEqual(t, GetVersions().ServerVersion.Format("v%s.%s.0"), kubeVersion)
 		})
 }
 
@@ -349,7 +350,7 @@ func TestHelm2WithDependencies(t *testing.T) {
 }
 
 func TestHelmWithDependenciesLegacyRepo(t *testing.T) {
-	testHelmWithDependencies(t, "helm2-with-dependencies", false)
+	testHelmWithDependencies(t, "helm-with-dependencies", true)
 }
 
 func testHelmWithDependencies(t *testing.T, chartPath string, legacyRepo bool) {
@@ -382,9 +383,13 @@ func testHelmWithDependencies(t *testing.T, chartPath string, legacyRepo bool) {
 		ctx = ctx.HelmRepoAdded("custom-repo")
 	}
 
+	helmVer := ""
+	if strings.Contains(chartPath, "helm2") {
+		helmVer = "v2"
+	}
 	ctx.Path(chartPath).
 		When().
-		Create().
+		Create("--helm-version", helmVer).
 		Sync().
 		Then().
 		Expect(SyncStatusIs(SyncStatusCodeSynced))
diff --git a/test/e2e/testdata/helm-kube-version/Chart.yaml b/test/e2e/testdata/helm-kube-version/Chart.yaml
index 4a3a5f239..62a5a8dcc 100644
--- a/test/e2e/testdata/helm-kube-version/Chart.yaml
+++ b/test/e2e/testdata/helm-kube-version/Chart.yaml
@@ -1,3 +1,3 @@
 version: 1.0.0
 name: helm-kube-version
-kubeVersion: 1.0.0
\ No newline at end of file
+kubeVersion: ">=1.0.0"
\ No newline at end of file
diff --git a/util/env/env_test.go b/util/env/env_test.go
index bbd51b59c..53843690b 100644
--- a/util/env/env_test.go
+++ b/util/env/env_test.go
@@ -4,6 +4,7 @@ import (
 	"io"
 	"os"
 	"testing"
+	"time"
 
 	util "github.com/argoproj/argo-cd/util/io"
 
@@ -60,3 +61,45 @@ func TestParseNumFromEnv_OutOfRangeValueSet(t *testing.T) {
 
 	assert.Equal(t, 10, num)
 }
+
+func TestParseDurationFromEnv(t *testing.T) {
+	testKey := "key"
+	defaultVal := 2 * time.Second
+	min := 1 * time.Second
+	max := 3 * time.Second
+
+	testCases := []struct {
+		name     string
+		env      string
+		expected time.Duration
+	}{{
+		name:     "EnvNotSet",
+		expected: defaultVal,
+	}, {
+		name:     "ValidValueSet",
+		env:      "2s",
+		expected: time.Second * 2,
+	}, {
+		name:     "MoreThanMaxSet",
+		env:      "5s",
+		expected: defaultVal,
+	}, {
+		name:     "LessThanMinSet",
+		env:      "-1s",
+		expected: defaultVal,
+	}, {
+		name:     "InvalidSet",
+		env:      "hello",
+		expected: defaultVal,
+	}}
+
+	for i, tc := range testCases {
+		t.Run(testCases[i].name, func(t *testing.T) {
+			tc = testCases[i]
+			setEnv(t, testKey, tc.env)
+
+			val := ParseDurationFromEnv(testKey, defaultVal, min, max)
+			assert.Equal(t, tc.expected, val)
+		})
+	}
+}
diff --git a/util/helm/client_test.go b/util/helm/client_test.go
index 44e87cb1f..9d869667a 100644
--- a/util/helm/client_test.go
+++ b/util/helm/client_test.go
@@ -3,6 +3,7 @@ package helm
 import (
 	"os"
 	"testing"
+	"time"
 
 	"github.com/Masterminds/semver"
 	"github.com/stretchr/testify/assert"
@@ -13,12 +14,12 @@ import (
 func TestIndex(t *testing.T) {
 	t.Run("Invalid", func(t *testing.T) {
 		client := NewClient("", Creds{}, false)
-		_, err := client.GetIndex()
+		_, err := client.GetIndex(false)
 		assert.Error(t, err)
 	})
 	t.Run("Stable", func(t *testing.T) {
 		client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false)
-		index, err := client.GetIndex()
+		index, err := client.GetIndex(false)
 		assert.NoError(t, err)
 		assert.NotNil(t, index)
 	})
@@ -27,10 +28,24 @@ func TestIndex(t *testing.T) {
 			Username: "my-password",
 			Password: "my-username",
 		}, false)
-		index, err := client.GetIndex()
+		index, err := client.GetIndex(false)
 		assert.NoError(t, err)
 		assert.NotNil(t, index)
 	})
+
+	t.Run("Cached", func(t *testing.T) {
+		var prev time.Duration
+		indexDuration, prev = time.Minute, indexDuration
+		defer func() {
+			indexDuration = prev
+		}()
+
+		client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false)
+		index, err := client.GetIndex(false)
+		assert.NoError(t, err)
+		assert.NotNil(t, index)
+	})
+
 }
 
 func Test_nativeHelmChart_ExtractChart(t *testing.T) {
diff --git a/util/helm/cmd_test.go b/util/helm/cmd_test.go
index 71aa6a6c3..62c5eca0f 100644
--- a/util/helm/cmd_test.go
+++ b/util/helm/cmd_test.go
@@ -1,6 +1,7 @@
 package helm
 
 import (
+	"log"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -33,3 +34,15 @@ func TestNewCmd_helmV3(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "helm", cmd.HelmVer.binaryName)
 }
+
+func TestNewCmd_helmDefaultVersion(t *testing.T) {
+	cmd, err := NewCmd(".", "")
+	assert.NoError(t, err)
+	assert.Equal(t, "helm", cmd.HelmVer.binaryName)
+}
+
+func TestNewCmd_helmInvalidVersion(t *testing.T) {
+	_, err := NewCmd(".", "abcd")
+	log.Println(err)
+	assert.EqualError(t, err, "helm chart version 'abcd' is not supported")
+}
diff --git a/util/helm/helmver_test.go b/util/helm/helmver_test.go
deleted file mode 100644
index 7b330cd77..000000000
--- a/util/helm/helmver_test.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package helm
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestGetHelmVersion_Helm3(t *testing.T) {
-	ver, err := getHelmVersion("./testdata/minio")
-	assert.NoError(t, err)
-	assert.Equal(t, ver.binaryName, HelmV3.binaryName)
-}
-
-func TestGetHelmVersion_Helm2(t *testing.T) {
-	ver, err := getHelmVersion("./testdata/helm2-dependency")
-	assert.NoError(t, err)
-	assert.Equal(t, ver.binaryName, HelmV2.binaryName)
-}
-
-func TestGetHelmVersion_InvalidVersion(t *testing.T) {
-	_, err := getHelmVersion("./testdata/invalid-version")
-	assert.Error(t, err)
-}
diff --git a/util/helm/testdata/invalid-version/Chart.yaml b/util/helm/testdata/invalid-version/Chart.yaml
deleted file mode 100755
index c9599f792..000000000
--- a/util/helm/testdata/invalid-version/Chart.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-apiVersion: v99
-version: 1.0.0
-name: my-chart