Repository: helm/helm (go)

Issue:
Override path for helm data
The only way to specify the path to where data (same for cache and config) are suppose to live is either using the default path (that uses the $HOME variable) or by setting the XDG_DATA_HOME.
This is a problem if you not want to use the default path and can't use the XDG_DATA_HOME override (e.g. because of some dependency that uses the same variable)

Suggestion:
Add a second way of overriding the default path. A new variable (something like `HELM_DATA_HOME`) that overrides both the default path AND the XDG_DATA_HOME variable.

This would be useful in github workflow which overwrites the HOME variable and attaches a volume to that path when executing a container such that plugins installed in the home folder at build time will be lost. 

Pull request:
Adding Helm env vars where XDG exposed
Helm had been exposing XDG based variables to end users. This lead
to confusion. For example, if a user wanted to change the cache
location Helm used should they change the XDG variable? Since this
would be like changing the HOME environment variable the answer
is no.

This change adds HELM_*_HOME environment variables to be used
in addition to XDG ones of the same name. Helm will now look
for the Helm specific variable. If not set, Helm will fall
back to XDG locations. If those are not set a default location
will be used. This keeps XDG in use as a default when present,
provides users with the ability to set the location, and removes
XDG from being exposed to end users to avoid confusion.

Closes #7919

@marckhouzam Would this solve for #6890, too?

Patch:
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index ea66a061a..143745f29 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -45,19 +45,19 @@ Environment variables:
 
 | Name                               | Description                                                                       |
 |------------------------------------|-----------------------------------------------------------------------------------|
-| $XDG_CACHE_HOME                    | set an alternative location for storing cached files.                             |
-| $XDG_CONFIG_HOME                   | set an alternative location for storing Helm configuration.                       |
-| $XDG_DATA_HOME                     | set an alternative location for storing Helm data.                                |
+| $HELM_CACHE_HOME                   | set an alternative location for storing cached files.                             |
+| $HELM_CONFIG_HOME                  | set an alternative location for storing Helm configuration.                       |
+| $HELM_DATA_HOME                    | set an alternative location for storing Helm data.                                |
 | $HELM_DRIVER                       | set the backend storage driver. Values are: configmap, secret, memory, postgres   |
 | $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use.                      |
 | $HELM_NO_PLUGINS                   | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.                        |
 | $KUBECONFIG                        | set an alternative Kubernetes configuration file (default "~/.kube/config")       |
 
-Helm stores configuration based on the XDG base directory specification, so
+Helm stores cache, configuration, and data based on the following configuration order:
 
-- cached files are stored in $XDG_CACHE_HOME/helm
-- configuration is stored in $XDG_CONFIG_HOME/helm
-- data is stored in $XDG_DATA_HOME/helm
+- If a HELM_*_HOME environment variable is set, it will be used
+- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
+- When no other location is set a default location will be used based on the operating system
 
 By default, the default directories depend on the Operating System. The defaults are listed below:
 
diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go
index 2e64e0810..d62f57a55 100644
--- a/pkg/cli/environment.go
+++ b/pkg/cli/environment.go
@@ -105,6 +105,9 @@ func envOr(name, def string) string {
 func (s *EnvSettings) EnvVars() map[string]string {
 	envvars := map[string]string{
 		"HELM_BIN":               os.Args[0],
+		"HELM_CACHE_HOME":        helmpath.CachePath(""),
+		"HELM_CONFIG_HOME":       helmpath.ConfigPath(""),
+		"HELM_DATA_HOME":         helmpath.DataPath(""),
 		"HELM_DEBUG":             fmt.Sprint(s.Debug),
 		"HELM_PLUGINS":           s.PluginsDirectory,
 		"HELM_REGISTRY_CONFIG":   s.RegistryConfig,
diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go
index 0b9068671..22d7bf0a1 100644
--- a/pkg/helmpath/lazypath.go
+++ b/pkg/helmpath/lazypath.go
@@ -20,11 +20,34 @@ import (
 	"helm.sh/helm/v3/pkg/helmpath/xdg"
 )
 
+const (
+	// CacheHomeEnvVar is the environment variable used by Helm
+	// for the cache directory. When no value is set a default is used.
+	CacheHomeEnvVar = "HELM_CACHE_HOME"
+
+	// ConfigHomeEnvVar is the environment variable used by Helm
+	// for the config directory. When no value is set a default is used.
+	ConfigHomeEnvVar = "HELM_CONFIG_HOME"
+
+	// DataHomeEnvVar is the environment variable used by Helm
+	// for the data directory. When no value is set a default is used.
+	DataHomeEnvVar = "HELM_DATA_HOME"
+)
+
 // lazypath is an lazy-loaded path buffer for the XDG base directory specification.
 type lazypath string
 
-func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) string {
-	base := os.Getenv(envVar)
+func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
+
+	// There is an order to checking for a path.
+	// 1. See if a Helm specific environment variable has been set.
+	// 2. Check if an XDG environment variable is set
+	// 3. Fall back to a default
+	base := os.Getenv(helmEnvVar)
+	if base != "" {
+		return filepath.Join(base, filepath.Join(elem...))
+	}
+	base = os.Getenv(xdgEnvVar)
 	if base == "" {
 		base = defaultFn()
 	}
@@ -34,16 +57,16 @@ func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) s
 // cachePath defines the base directory relative to which user specific non-essential data files
 // should be stored.
 func (l lazypath) cachePath(elem ...string) string {
-	return l.path(xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
+	return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
 }
 
 // configPath defines the base directory relative to which user specific configuration files should
 // be stored.
 func (l lazypath) configPath(elem ...string) string {
-	return l.path(xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...))
+	return l.path(ConfigHomeEnvVar, xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...))
 }
 
 // dataPath defines the base directory relative to which user specific data files should be stored.
 func (l lazypath) dataPath(elem ...string) string {
-	return l.path(xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
+	return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
 }


Test patch:
diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go
index e1fa1fc27..891bb698e 100644
--- a/cmd/helm/root_test.go
+++ b/cmd/helm/root_test.go
@@ -55,6 +55,24 @@ func TestRootCmd(t *testing.T) {
 			envvars:  map[string]string{xdg.DataHomeEnvVar: "/bar"},
 			dataPath: "/bar/helm",
 		},
+		{
+			name:      "with $HELM_CACHE_HOME set",
+			args:      "env",
+			envvars:   map[string]string{helmpath.CacheHomeEnvVar: "/foo/helm"},
+			cachePath: "/foo/helm",
+		},
+		{
+			name:       "with $HELM_CONFIG_HOME set",
+			args:       "env",
+			envvars:    map[string]string{helmpath.ConfigHomeEnvVar: "/foo/helm"},
+			configPath: "/foo/helm",
+		},
+		{
+			name:     "with $HELM_DATA_HOME set",
+			args:     "env",
+			envvars:  map[string]string{helmpath.DataHomeEnvVar: "/foo/helm"},
+			dataPath: "/foo/helm",
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/test/ensure/ensure.go b/internal/test/ensure/ensure.go
index b4775df80..6219ad626 100644
--- a/internal/test/ensure/ensure.go
+++ b/internal/test/ensure/ensure.go
@@ -21,6 +21,7 @@ import (
 	"os"
 	"testing"
 
+	"helm.sh/helm/v3/pkg/helmpath"
 	"helm.sh/helm/v3/pkg/helmpath/xdg"
 )
 
@@ -31,6 +32,9 @@ func HelmHome(t *testing.T) func() {
 	os.Setenv(xdg.CacheHomeEnvVar, base)
 	os.Setenv(xdg.ConfigHomeEnvVar, base)
 	os.Setenv(xdg.DataHomeEnvVar, base)
+	os.Setenv(helmpath.CacheHomeEnvVar, "")
+	os.Setenv(helmpath.ConfigHomeEnvVar, "")
+	os.Setenv(helmpath.DataHomeEnvVar, "")
 	return func() {
 		os.RemoveAll(base)
 	}