feat(cli): support XDG base directory specification

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>
pull/5443/head
Matthew Fisher 7 years ago
parent d360705c83
commit c728611e5a
No known key found for this signature in database
GPG Key ID: 92AA783CBAAE8E3B

@ -68,13 +68,13 @@ test: test-unit
test-unit: vendor test-unit: vendor
@echo @echo
@echo "==> Running unit tests <==" @echo "==> Running unit tests <=="
HELM_HOME=/no_such_dir go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
.PHONY: test-coverage .PHONY: test-coverage
test-coverage: vendor test-coverage: vendor
@echo @echo
@echo "==> Running unit tests with coverage <==" @echo "==> Running unit tests with coverage <=="
@ HELM_HOME=/no_such_dir ./scripts/coverage.sh @ ./scripts/coverage.sh
.PHONY: test-style .PHONY: test-style
test-style: vendor $(GOLANGCI_LINT) test-style: vendor $(GOLANGCI_LINT)

@ -26,6 +26,7 @@ import (
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
) )
const createDesc = ` const createDesc = `
@ -86,7 +87,7 @@ func (o *createOptions) run(out io.Writer) error {
if o.starter != "" { if o.starter != "" {
// Create from the starter // Create from the starter
lstarter := filepath.Join(settings.Home.Starters(), o.starter) lstarter := filepath.Join(helmpath.Starters(), o.starter)
// If path is absolute, we dont want to prefix it with helm starters folder // If path is absolute, we dont want to prefix it with helm starters folder
if filepath.IsAbs(o.starter) { if filepath.IsAbs(o.starter) {
lstarter = o.starter lstarter = o.starter

@ -23,16 +23,18 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
) )
func TestCreateCmd(t *testing.T) { func TestCreateCmd(t *testing.T) {
tdir := testTempDir(t)
defer testChdir(t, tdir)()
cname := "testchart" cname := "testchart"
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
defer testChdir(t, helmpath.CachePath())()
// Run a create // Run a create
if _, _, err := executeActionCommand("create " + cname); err != nil { if _, _, err := executeActionCommand("create " + cname); err != nil {
@ -61,17 +63,14 @@ func TestCreateCmd(t *testing.T) {
} }
func TestCreateStarterCmd(t *testing.T) { func TestCreateStarterCmd(t *testing.T) {
defer resetEnv()()
cname := "testchart" cname := "testchart"
// Make a temp dir defer resetEnv()()
tdir := testTempDir(t) ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
hh := testHelmHome(t) defer testChdir(t, helmpath.CachePath())()
settings.Home = hh
// Create a starter. // Create a starter.
starterchart := hh.Starters() starterchart := helmpath.Starters()
os.Mkdir(starterchart, 0755) os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
@ -83,10 +82,8 @@ func TestCreateStarterCmd(t *testing.T) {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
defer testChdir(t, tdir)()
// Run a create // Run a create
if _, _, err := executeActionCommand(fmt.Sprintf("--home='%s' create --starter=starterchart %s", hh.String(), cname)); err != nil { if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=starterchart %s", cname)); err != nil {
t.Errorf("Failed to run create: %s", err) t.Errorf("Failed to run create: %s", err)
return return
} }
@ -131,16 +128,12 @@ func TestCreateStarterCmd(t *testing.T) {
func TestCreateStarterAbsoluteCmd(t *testing.T) { func TestCreateStarterAbsoluteCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
cname := "testchart" cname := "testchart"
// Make a temp dir
tdir := testTempDir(t)
hh := testHelmHome(t)
settings.Home = hh
// Create a starter. // Create a starter.
starterchart := hh.Starters() starterchart := helmpath.Starters()
os.Mkdir(starterchart, 0755) os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
@ -152,12 +145,12 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
defer testChdir(t, tdir)() defer testChdir(t, helmpath.CachePath())()
starterChartPath := filepath.Join(starterchart, "starterchart") starterChartPath := filepath.Join(starterchart, "starterchart")
// Run a create // Run a create
if _, _, err := executeActionCommand(fmt.Sprintf("--home='%s' create --starter=%s %s", hh.String(), starterChartPath, cname)); err != nil { if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=%s %s", starterChartPath, cname)); err != nil {
t.Errorf("Failed to run create: %s", err) t.Errorf("Failed to run create: %s", err)
return return
} }

@ -56,7 +56,6 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: chartpath, ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
} }

@ -18,9 +18,12 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/provenance"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
@ -29,21 +32,21 @@ import (
func TestDependencyBuildCmd(t *testing.T) { func TestDependencyBuildCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(hh.String()) srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
if _, err := srv.CopyCharts("testdata/testcharts/*.tgz"); err != nil { if _, err := srv.CopyCharts("testdata/testcharts/*.tgz"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
chartname := "depbuild" chartname := "depbuild"
if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
cmd := fmt.Sprintf("--home='%s' dependency build '%s'", hh, hh.Path(chartname)) cmd := fmt.Sprintf("dependency build '%s'", filepath.Join(helmpath.DataPath(), chartname))
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
@ -57,14 +60,14 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
// Make sure the actual file got downloaded. // Make sure the actual file got downloaded.
expect := hh.Path(chartname, "charts/reqtest-0.1.0.tgz") expect := filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// In the second pass, we want to remove the chart's request dependency, // In the second pass, we want to remove the chart's request dependency,
// then see if it restores from the lock. // then see if it restores from the lock.
lockfile := hh.Path(chartname, "Chart.lock") lockfile := filepath.Join(helmpath.DataPath(), chartname, "Chart.lock")
if _, err := os.Stat(lockfile); err != nil { if _, err := os.Stat(lockfile); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -79,7 +82,7 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
// Now repeat the test that the dependency exists. // Now repeat the test that the dependency exists.
expect = hh.Path(chartname, "charts/reqtest-0.1.0.tgz") expect = filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -90,7 +93,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
i, err := repo.LoadIndexFile(hh.CacheIndex("test")) i, err := repo.LoadIndexFile(helmpath.CacheIndex("test"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -60,7 +60,6 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: chartpath, ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),

@ -23,8 +23,10 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/provenance"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
@ -33,10 +35,10 @@ import (
func TestDependencyUpdateCmd(t *testing.T) { func TestDependencyUpdateCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(hh.String()) srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
if err != nil { if err != nil {
@ -48,11 +50,11 @@ func TestDependencyUpdateCmd(t *testing.T) {
chartname := "depup" chartname := "depup"
ch := createTestingMetadata(chartname, srv.URL()) ch := createTestingMetadata(chartname, srv.URL())
md := ch.Metadata md := ch.Metadata
if err := chartutil.SaveDir(ch, hh.String()); err != nil { if err := chartutil.SaveDir(ch, helmpath.DataPath()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, out, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update '%s'", hh.String(), hh.Path(chartname))) _, out, err := executeActionCommand(fmt.Sprintf("dependency update '%s'", filepath.Join(helmpath.DataPath(), chartname)))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -64,7 +66,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
// Make sure the actual file got downloaded. // Make sure the actual file got downloaded.
expect := hh.Path(chartname, "charts/reqtest-0.1.0.tgz") expect := filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -74,7 +76,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
i, err := repo.LoadIndexFile(hh.CacheIndex("test")) i, err := repo.LoadIndexFile(helmpath.CacheIndex("test"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -90,12 +92,12 @@ func TestDependencyUpdateCmd(t *testing.T) {
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()}, {Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
} }
dir := hh.Path(chartname, "Chart.yaml") dir := filepath.Join(helmpath.DataPath(), chartname, "Chart.yaml")
if err := chartutil.SaveChartfile(dir, md); err != nil { if err := chartutil.SaveChartfile(dir, md); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, out, err = executeActionCommand(fmt.Sprintf("--home='%s' dependency update '%s'", hh, hh.Path(chartname))) _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s'", filepath.Join(helmpath.DataPath(), chartname)))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -103,11 +105,11 @@ func TestDependencyUpdateCmd(t *testing.T) {
// In this second run, we should see compressedchart-0.3.0.tgz, and not // In this second run, we should see compressedchart-0.3.0.tgz, and not
// the 0.1.0 version. // the 0.1.0 version.
expect = hh.Path(chartname, "charts/compressedchart-0.3.0.tgz") expect = filepath.Join(helmpath.DataPath(), chartname, "charts/compressedchart-0.3.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatalf("Expected %q: %s", expect, err) t.Fatalf("Expected %q: %s", expect, err)
} }
dontExpect := hh.Path(chartname, "charts/compressedchart-0.1.0.tgz") dontExpect := filepath.Join(helmpath.DataPath(), chartname, "charts/compressedchart-0.1.0.tgz")
if _, err := os.Stat(dontExpect); err == nil { if _, err := os.Stat(dontExpect); err == nil {
t.Fatalf("Unexpected %q", dontExpect) t.Fatalf("Unexpected %q", dontExpect)
} }
@ -116,10 +118,10 @@ func TestDependencyUpdateCmd(t *testing.T) {
func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) { func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
defer resetEnv()() defer resetEnv()()
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(hh.String()) srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
if err != nil { if err != nil {
@ -129,11 +131,11 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
chartname := "depup" chartname := "depup"
if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, out, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update --skip-refresh %s", hh, hh.Path(chartname))) _, out, err := executeActionCommand(fmt.Sprintf("dependency update --skip-refresh %s", filepath.Join(helmpath.DataPath(), chartname)))
if err == nil { if err == nil {
t.Fatal("Expected failure to find the repo with skipRefresh") t.Fatal("Expected failure to find the repo with skipRefresh")
} }
@ -147,10 +149,10 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(hh.String()) srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
if err != nil { if err != nil {
@ -160,11 +162,11 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
chartname := "depupdelete" chartname := "depupdelete"
if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, output, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname))) _, output, err := executeActionCommand(fmt.Sprintf("dependency update %s", filepath.Join(helmpath.DataPath(), chartname)))
if err != nil { if err != nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
@ -173,14 +175,14 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
_, output, err = executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname))) _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s", filepath.Join(helmpath.DataPath(), chartname)))
if err == nil { if err == nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")
} }
// Make sure charts dir still has dependencies // Make sure charts dir still has dependencies
files, err := ioutil.ReadDir(filepath.Join(hh.Path(chartname), "charts")) files, err := ioutil.ReadDir(filepath.Join(filepath.Join(helmpath.DataPath(), chartname), "charts"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -196,7 +198,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
} }
// Make sure tmpcharts is deleted // Make sure tmpcharts is deleted
if _, err := os.Stat(filepath.Join(hh.Path(chartname), "tmpcharts")); !os.IsNotExist(err) { if _, err := os.Stat(filepath.Join(filepath.Join(helmpath.DataPath(), chartname), "tmpcharts")); !os.IsNotExist(err) {
t.Fatalf("tmpcharts dir still exists") t.Fatalf("tmpcharts dir still exists")
} }
} }

@ -30,10 +30,8 @@ import (
"helm.sh/helm/internal/test" "helm.sh/helm/internal/test"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
kubefake "helm.sh/helm/pkg/kube/fake" kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage" "helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver" "helm.sh/helm/pkg/storage/driver"
) )
@ -45,20 +43,10 @@ func init() {
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
os.Unsetenv("HELM_HOME")
exitCode := m.Run() exitCode := m.Run()
os.Exit(exitCode) os.Exit(exitCode)
} }
func testTempDir(t *testing.T) string {
t.Helper()
d, err := ioutil.TempDir("", "helm")
if err != nil {
t.Fatal(err)
}
return d
}
func runTestCmd(t *testing.T, tests []cmdTestCase) { func runTestCmd(t *testing.T, tests []cmdTestCase) {
t.Helper() t.Helper()
for _, tt := range tests { for _, tt := range tests {
@ -144,48 +132,6 @@ func executeActionCommand(cmd string) (*cobra.Command, string, error) {
return executeActionCommandC(storageFixture(), cmd) return executeActionCommandC(storageFixture(), cmd)
} }
// ensureTestHome creates a home directory like ensureHome, but without remote references.
func ensureTestHome(t *testing.T, home helmpath.Home) {
t.Helper()
for _, p := range []string{
home.String(),
home.Repository(),
home.Cache(),
home.Plugins(),
home.Starters(),
} {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatal(err)
}
}
repoFile := home.RepositoryFile()
if _, err := os.Stat(repoFile); err != nil {
rf := repo.NewFile()
rf.Add(&repo.Entry{
Name: "charts",
URL: "http://example.com/foo",
Cache: "charts-index.yaml",
})
if err := rf.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
if r, err := repo.LoadFile(repoFile); err == repo.ErrRepoOutOfDate {
if err := r.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
}
// testHelmHome sets up a Helm Home in a temp dir.
func testHelmHome(t *testing.T) helmpath.Home {
t.Helper()
dir := helmpath.Home(testTempDir(t))
ensureTestHome(t, dir)
return dir
}
func resetEnv() func() { func resetEnv() func() {
origSettings, origEnv := settings, os.Environ() origSettings, origEnv := settings, os.Environ()
return func() { return func() {

@ -1,52 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
)
var longHomeHelp = `
This command displays the location of HELM_HOME. This is where
any helm configuration files live.
`
func newHomeCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "home",
Short: "displays the location of HELM_HOME",
Long: longHomeHelp,
Args: require.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
h := settings.Home
fmt.Fprintln(out, h)
if settings.Debug {
fmt.Fprintf(out, "Repository: %s\n", h.Repository())
fmt.Fprintf(out, "RepositoryFile: %s\n", h.RepositoryFile())
fmt.Fprintf(out, "Cache: %s\n", h.Cache())
fmt.Fprintf(out, "Starters: %s\n", h.Starters())
fmt.Fprintf(out, "Plugins: %s\n", h.Plugins())
}
},
}
return cmd
}

@ -35,14 +35,28 @@ import (
) )
const initDesc = ` const initDesc = `
This command sets up local configuration in $HELM_HOME (default ~/.helm/). This command sets up local configuration.
Helm stores configuration based on the XDG base directory specification, so
- 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
By default, the default directories depend on the Operating System. The defaults are listed below:
+------------------+---------------------------+--------------------------------+-------------------------+
| Operating System | Cache Path | Configuration Path | Data Path |
+------------------+---------------------------+--------------------------------+-------------------------+
| Linux | $HOME/.cache/helm | $HOME/.config/helm | $HOME/.local/share/helm |
| macOS | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm |
| Windows | %TEMP%\helm | %APPDATA%\helm | %APPDATA%\helm |
+------------------+---------------------------+--------------------------------+-------------------------+
` `
type initOptions struct { type initOptions struct {
skipRefresh bool // --skip-refresh skipRefresh bool // --skip-refresh
pluginsFilename string // --plugins pluginsFilename string // --plugins
home helmpath.Home
} }
type pluginsFileEntry struct { type pluginsFileEntry struct {
@ -63,7 +77,6 @@ func newInitCmd(out io.Writer) *cobra.Command {
Long: initDesc, Long: initDesc,
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out) return o.run(out)
}, },
} }
@ -77,13 +90,13 @@ func newInitCmd(out io.Writer) *cobra.Command {
// run initializes local config. // run initializes local config.
func (o *initOptions) run(out io.Writer) error { func (o *initOptions) run(out io.Writer) error {
if err := ensureDirectories(o.home, out); err != nil { if err := ensureDirectories(out); err != nil {
return err return err
} }
if err := ensureReposFile(o.home, out, o.skipRefresh); err != nil { if err := ensureReposFile(out, o.skipRefresh); err != nil {
return err return err
} }
if err := ensureRepoFileFormat(o.home.RepositoryFile(), out); err != nil { if err := ensureRepoFileFormat(helmpath.RepositoryFile(), out); err != nil {
return err return err
} }
if o.pluginsFilename != "" { if o.pluginsFilename != "" {
@ -91,24 +104,29 @@ func (o *initOptions) run(out io.Writer) error {
return err return err
} }
} }
fmt.Fprintf(out, "$HELM_HOME has been configured at %s.\n", settings.Home) fmt.Fprintln(out, "Helm is now configured to use the following directories:")
fmt.Fprintf(out, "Cache: %s\n", helmpath.CachePath())
fmt.Fprintf(out, "Configuration: %s\n", helmpath.ConfigPath())
fmt.Fprintf(out, "Data: %s\n", helmpath.DataPath())
fmt.Fprintln(out, "Happy Helming!") fmt.Fprintln(out, "Happy Helming!")
return nil return nil
} }
// ensureDirectories checks to see if $HELM_HOME exists. // ensureDirectories checks to see if the directories Helm uses exists.
// //
// If $HELM_HOME does not exist, this function will create it. // If they do not exist, this function will create it.
func ensureDirectories(home helmpath.Home, out io.Writer) error { func ensureDirectories(out io.Writer) error {
configDirectories := []string{ directories := []string{
home.String(), helmpath.CachePath(),
home.Repository(), helmpath.ConfigPath(),
home.Cache(), helmpath.DataPath(),
home.Plugins(), helmpath.RepositoryCache(),
home.Starters(), helmpath.Plugins(),
home.Archive(), helmpath.PluginCache(),
} helmpath.Starters(),
for _, p := range configDirectories { helmpath.Archive(),
}
for _, p := range directories {
if fi, err := os.Stat(p); err != nil { if fi, err := os.Stat(p); err != nil {
fmt.Fprintf(out, "Creating %s \n", p) fmt.Fprintf(out, "Creating %s \n", p)
if err := os.MkdirAll(p, 0755); err != nil { if err := os.MkdirAll(p, 0755); err != nil {
@ -122,8 +140,8 @@ func ensureDirectories(home helmpath.Home, out io.Writer) error {
return nil return nil
} }
func ensureReposFile(home helmpath.Home, out io.Writer, skipRefresh bool) error { func ensureReposFile(out io.Writer, skipRefresh bool) error {
repoFile := home.RepositoryFile() repoFile := helmpath.RepositoryFile()
if fi, err := os.Stat(repoFile); err != nil { if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile) fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewFile() f := repo.NewFile()
@ -168,7 +186,7 @@ func ensurePluginsInstalled(pluginsFilename string, out io.Writer) error {
} }
func ensurePluginInstalled(requiredPlugin *pluginsFileEntry, pluginsFilename string, out io.Writer) error { func ensurePluginInstalled(requiredPlugin *pluginsFileEntry, pluginsFilename string, out io.Writer) error {
i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version, settings.Home) i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version)
if err != nil { if err != nil {
return err return err
} }

@ -21,33 +21,34 @@ import (
"os" "os"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
const testPluginsFile = "testdata/plugins.yaml" const testPluginsFile = "testdata/plugins.yaml"
func TestEnsureHome(t *testing.T) { func TestEnsureHome(t *testing.T) {
hh := helmpath.Home(testTempDir(t)) ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
settings.Home = hh if err := ensureDirectories(b); err != nil {
if err := ensureDirectories(hh, b); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensureReposFile(hh, b, false); err != nil { if err := ensureReposFile(b, false); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensureReposFile(hh, b, true); err != nil { if err := ensureReposFile(b, true); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { if err := ensureRepoFileFormat(helmpath.RepositoryFile(), b); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensurePluginsInstalled(testPluginsFile, b); err != nil { if err := ensurePluginsInstalled(testPluginsFile, b); err != nil {
t.Error(err) t.Error(err)
} }
expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache()} expectedDirs := []string{helmpath.CachePath(), helmpath.ConfigPath(), helmpath.DataPath()}
for _, dir := range expectedDirs { for _, dir := range expectedDirs {
if fi, err := os.Stat(dir); err != nil { if fi, err := os.Stat(dir); err != nil {
t.Errorf("%s", err) t.Errorf("%s", err)
@ -56,13 +57,13 @@ func TestEnsureHome(t *testing.T) {
} }
} }
if fi, err := os.Stat(hh.RepositoryFile()); err != nil { if fi, err := os.Stat(helmpath.RepositoryFile()); err != nil {
t.Error(err) t.Error(err)
} else if fi.IsDir() { } else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi) t.Errorf("%s should not be a directory", fi)
} }
if plugins, err := findPlugins(settings.PluginDirs()); err != nil { if plugins, err := findPlugins(helmpath.Plugins()); err != nil {
t.Error(err) t.Error(err)
} else if len(plugins) != 1 { } else if len(plugins) != 1 {
t.Errorf("Expected 1 plugin, got %d", len(plugins)) t.Errorf("Expected 1 plugin, got %d", len(plugins))

@ -195,7 +195,6 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: cp, ChartPath: cp,
HelmHome: settings.Home,
Keyring: client.ChartPathOptions.Keyring, Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false, SkipUpdate: false,
Getters: getter.All(settings), Getters: getter.All(settings),

@ -26,6 +26,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
@ -41,8 +42,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
return return
} }
// debug("HELM_PLUGIN_DIRS=%s", settings.PluginDirs()) found, err := findPlugins(helmpath.Plugins())
found, err := findPlugins(settings.PluginDirs())
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return return
@ -113,7 +113,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []string{} unknown := []string{}
kvargs := []string{"--context", "--home", "--namespace"} kvargs := []string{"--context", "--namespace"}
knownArg := func(a string) bool { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {
@ -126,7 +126,7 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
switch a := args[i]; a { switch a := args[i]; a {
case "--debug": case "--debug":
known = append(known, a) known = append(known, a)
case "--context", "--home", "--namespace": case "--context", "--namespace":
known = append(known, a, args[i+1]) known = append(known, a, args[i+1])
i++ i++
default: default:

@ -74,7 +74,6 @@ func newPackageCmd(out io.Writer) *cobra.Command {
downloadManager := &downloader.Manager{ downloadManager := &downloader.Manager{
Out: ioutil.Discard, Out: ioutil.Discard,
ChartPath: path, ChartPath: path,
HelmHome: settings.Home,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
Debug: settings.Debug, Debug: settings.Debug,

@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
@ -137,19 +138,16 @@ func TestPackage(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tmp := testTempDir(t)
t.Logf("Running tests in %s", tmp) ensure.HelmHome(t)
defer testChdir(t, tmp)() defer ensure.CleanHomeDirs(t)
t.Logf("Running tests in %s", helmpath.CachePath())
defer testChdir(t, helmpath.CachePath())()
if err := os.Mkdir("toot", 0777); err != nil { if err := os.Mkdir("toot", 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
ensureTestHome(t, helmpath.Home(tmp))
settings.Home = helmpath.Home(tmp)
for _, tt := range tests { for _, tt := range tests {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
c := newPackageCmd(buf) c := newPackageCmd(buf)
@ -203,14 +201,13 @@ func TestSetAppVersion(t *testing.T) {
var ch *chart.Chart var ch *chart.Chart
expectedAppVersion := "app-version-foo" expectedAppVersion := "app-version-foo"
tmp := testTempDir(t)
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
c := newPackageCmd(&bytes.Buffer{}) c := newPackageCmd(&bytes.Buffer{})
flags := map[string]string{ flags := map[string]string{
"destination": tmp, "destination": helmpath.CachePath(),
"app-version": expectedAppVersion, "app-version": expectedAppVersion,
} }
setFlags(c, flags) setFlags(c, flags)
@ -218,7 +215,7 @@ func TestSetAppVersion(t *testing.T) {
t.Errorf("unexpected error %q", err) t.Errorf("unexpected error %q", err)
} }
chartPath := filepath.Join(tmp, "alpine-0.1.0.tgz") chartPath := filepath.Join(helmpath.CachePath(), "alpine-0.1.0.tgz")
if fi, err := os.Stat(chartPath); err != nil { if fi, err := os.Stat(chartPath); err != nil {
t.Errorf("expected file %q, got err %q", chartPath, err) t.Errorf("expected file %q, got err %q", chartPath, err)
} else if fi.Size() == 0 { } else if fi.Size() == 0 {
@ -270,8 +267,8 @@ func TestPackageValues(t *testing.T) {
}, },
} }
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
for _, tc := range testCases { for _, tc := range testCases {
var files []string var files []string
@ -292,7 +289,7 @@ func TestPackageValues(t *testing.T) {
func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[string]string, valueFiles string, expected chartutil.Values) { func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[string]string, valueFiles string, expected chartutil.Values) {
t.Helper() t.Helper()
outputDir := testTempDir(t) outputDir := ensure.TempDir(t)
if len(flags) == 0 { if len(flags) == 0 {
flags = make(map[string]string) flags = make(map[string]string)
@ -321,7 +318,7 @@ func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[str
} }
func createValuesFile(t *testing.T, data string) string { func createValuesFile(t *testing.T, data string) string {
outputDir := testTempDir(t) outputDir := ensure.TempDir(t)
outputFile := filepath.Join(outputDir, "values.yaml") outputFile := filepath.Join(outputDir, "values.yaml")
if err := ioutil.WriteFile(outputFile, []byte(data), 0755); err != nil { if err := ioutil.WriteFile(outputFile, []byte(data), 0755); err != nil {

@ -0,0 +1,79 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
)
var longPathHelp = `
This command displays the locations where Helm stores files.
To display a specific location, use 'helm path [config|data|cache]'.
`
var pathArgMap = map[string]string{
"config": helmpath.ConfigPath(),
"data": helmpath.DataPath(),
"cache": helmpath.CachePath(),
}
func newPathCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "path",
Short: "displays the locations where Helm stores files",
Aliases: []string{"home"},
Long: longPathHelp,
Args: require.MinimumNArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if p, ok := pathArgMap[args[0]]; ok {
fmt.Fprintln(out, p)
} else {
var validArgs []string
for arg := range pathArgMap {
validArgs = append(validArgs, arg)
}
return fmt.Errorf("invalid argument '%s'. Must be one of: %s", args[0], validArgs)
}
} else {
// NOTE(bacongobbler): the order here is important: we want to display the config path
// first so users can parse the first line to replicate Helm 2's `helm home`.
fmt.Fprintln(out, helmpath.ConfigPath())
fmt.Fprintln(out, helmpath.DataPath())
fmt.Fprintln(out, helmpath.CachePath())
if settings.Debug {
fmt.Fprintf(out, "Archive: %s\n", helmpath.Archive())
fmt.Fprintf(out, "PluginCache: %s\n", helmpath.PluginCache())
fmt.Fprintf(out, "Plugins: %s\n", helmpath.Plugins())
fmt.Fprintf(out, "Registry: %s\n", helmpath.Registry())
fmt.Fprintf(out, "RepositoryCache: %s\n", helmpath.RepositoryCache())
fmt.Fprintf(out, "RepositoryFile: %s\n", helmpath.RepositoryFile())
fmt.Fprintf(out, "Starters: %s\n", helmpath.Starters())
}
}
return nil
},
}
return cmd
}

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
"helm.sh/helm/pkg/plugin/installer" "helm.sh/helm/pkg/plugin/installer"
) )
@ -30,7 +29,6 @@ import (
type pluginInstallOptions struct { type pluginInstallOptions struct {
source string source string
version string version string
home helmpath.Home
} }
const pluginInstallDesc = ` const pluginInstallDesc = `
@ -60,14 +58,13 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command {
func (o *pluginInstallOptions) complete(args []string) error { func (o *pluginInstallOptions) complete(args []string) error {
o.source = args[0] o.source = args[0]
o.home = settings.Home
return nil return nil
} }
func (o *pluginInstallOptions) run(out io.Writer) error { func (o *pluginInstallOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
i, err := installer.NewForSource(o.source, o.version, o.home) i, err := installer.NewForSource(o.source, o.version)
if err != nil { if err != nil {
return err return err
} }

@ -25,26 +25,13 @@ import (
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
type pluginListOptions struct {
home helmpath.Home
}
func newPluginListCmd(out io.Writer) *cobra.Command { func newPluginListCmd(out io.Writer) *cobra.Command {
o := &pluginListOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home debug("pluginDirs: %s", helmpath.Plugins())
return o.run(out) plugins, err := findPlugins(helmpath.Plugins())
},
}
return cmd
}
func (o *pluginListOptions) run(out io.Writer) error {
debug("pluginDirs: %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
if err != nil { if err != nil {
return err return err
} }
@ -56,4 +43,7 @@ func (o *pluginListOptions) run(out io.Writer) error {
} }
fmt.Fprintln(out, table) fmt.Fprintln(out, table)
return nil return nil
},
}
return cmd
} }

@ -30,7 +30,6 @@ import (
type pluginRemoveOptions struct { type pluginRemoveOptions struct {
names []string names []string
home helmpath.Home
} }
func newPluginRemoveCmd(out io.Writer) *cobra.Command { func newPluginRemoveCmd(out io.Writer) *cobra.Command {
@ -53,13 +52,12 @@ func (o *pluginRemoveOptions) complete(args []string) error {
return errors.New("please provide plugin name to remove") return errors.New("please provide plugin name to remove")
} }
o.names = args o.names = args
o.home = settings.Home
return nil return nil
} }
func (o *pluginRemoveOptions) run(out io.Writer) error { func (o *pluginRemoveOptions) run(out io.Writer) error {
debug("loading installed plugins from %s", settings.PluginDirs()) debug("loading installed plugins from %s", helmpath.Plugins())
plugins, err := findPlugins(settings.PluginDirs()) plugins, err := findPlugins(helmpath.Plugins())
if err != nil { if err != nil {
return err return err
} }

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
@ -39,11 +40,11 @@ func TestManuallyProcessArgs(t *testing.T) {
} }
expectKnown := []string{ expectKnown := []string{
"--debug", "--context", "test1", "--home=/tmp", "--debug", "--context", "test1",
} }
expectUnknown := []string{ expectUnknown := []string{
"--foo", "bar", "command", "--foo", "bar", "--home=/tmp", "command",
} }
known, unknown := manuallyProcessArgs(input) known, unknown := manuallyProcessArgs(input)
@ -64,10 +65,9 @@ func TestManuallyProcessArgs(t *testing.T) {
func TestLoadPlugins(t *testing.T) { func TestLoadPlugins(t *testing.T) {
defer resetEnv()() defer resetEnv()()
settings.Home = "testdata/helmhome" os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
os.Setenv("HELM_HOME", settings.Home.String()) os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
hh := settings.Home
out := bytes.NewBuffer(nil) out := bytes.NewBuffer(nil)
cmd := &cobra.Command{} cmd := &cobra.Command{}
@ -75,12 +75,13 @@ func TestLoadPlugins(t *testing.T) {
envs := strings.Join([]string{ envs := strings.Join([]string{
"fullenv", "fullenv",
hh.Plugins() + "/fullenv", helmpath.Plugins() + "/fullenv",
hh.Plugins(), helmpath.Plugins(),
hh.String(), helmpath.CachePath(),
hh.Repository(), helmpath.ConfigPath(),
hh.RepositoryFile(), helmpath.DataPath(),
hh.Cache(), helmpath.RepositoryFile(),
helmpath.RepositoryCache(),
os.Args[0], os.Args[0],
}, "\n") }, "\n")
@ -94,7 +95,7 @@ func TestLoadPlugins(t *testing.T) {
}{ }{
{"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}}, {"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}},
{"echo", "echo stuff", "This echos stuff", "hello\n", []string{}}, {"echo", "echo stuff", "This echos stuff", "hello\n", []string{}},
{"env", "env stuff", "show the env", hh.String() + "\n", []string{}}, {"env", "env stuff", "show the env", helmpath.DataPath() + "\n", []string{}},
{"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}}, {"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}},
} }
@ -134,7 +135,7 @@ func TestLoadPlugins(t *testing.T) {
func TestLoadPlugins_HelmNoPlugins(t *testing.T) { func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
defer resetEnv()() defer resetEnv()()
settings.Home = "testdata/helmhome" os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
os.Setenv("HELM_NO_PLUGINS", "1") os.Setenv("HELM_NO_PLUGINS", "1")
@ -151,8 +152,8 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
func TestSetupEnv(t *testing.T) { func TestSetupEnv(t *testing.T) {
defer resetEnv()() defer resetEnv()()
name := "pequod" name := "pequod"
settings.Home = helmpath.Home("testdata/helmhome") os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
base := filepath.Join(settings.Home.Plugins(), name) base := filepath.Join(helmpath.Plugins(), name)
settings.Debug = true settings.Debug = true
defer func() { defer func() {
settings.Debug = false settings.Debug = false
@ -165,13 +166,13 @@ func TestSetupEnv(t *testing.T) {
}{ }{
{"HELM_PLUGIN_NAME", name}, {"HELM_PLUGIN_NAME", name},
{"HELM_PLUGIN_DIR", base}, {"HELM_PLUGIN_DIR", base},
{"HELM_PLUGIN", settings.Home.Plugins()},
{"HELM_DEBUG", "1"}, {"HELM_DEBUG", "1"},
{"HELM_HOME", settings.Home.String()}, {"HELM_PATH_REPOSITORY_FILE", helmpath.RepositoryFile()},
{"HELM_PATH_REPOSITORY", settings.Home.Repository()}, {"HELM_PATH_CACHE", helmpath.CachePath()},
{"HELM_PATH_REPOSITORY_FILE", settings.Home.RepositoryFile()}, {"HELM_PATH_CONFIG", helmpath.ConfigPath()},
{"HELM_PATH_CACHE", settings.Home.Cache()}, {"HELM_PATH_DATA", helmpath.DataPath()},
{"HELM_PATH_STARTER", settings.Home.Starters()}, {"HELM_PATH_STARTER", helmpath.Starters()},
{"HELM_PLUGIN", helmpath.Plugins()},
} { } {
if got := os.Getenv(tt.name); got != tt.expect { if got := os.Getenv(tt.name); got != tt.expect {
t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got) t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got)

@ -31,7 +31,6 @@ import (
type pluginUpdateOptions struct { type pluginUpdateOptions struct {
names []string names []string
home helmpath.Home
} }
func newPluginUpdateCmd(out io.Writer) *cobra.Command { func newPluginUpdateCmd(out io.Writer) *cobra.Command {
@ -54,14 +53,13 @@ func (o *pluginUpdateOptions) complete(args []string) error {
return errors.New("please provide plugin name to update") return errors.New("please provide plugin name to update")
} }
o.names = args o.names = args
o.home = settings.Home
return nil return nil
} }
func (o *pluginUpdateOptions) run(out io.Writer) error { func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
debug("loading installed plugins from %s", settings.PluginDirs()) debug("loading installed plugins from %s", helmpath.Plugins())
plugins, err := findPlugins(settings.PluginDirs()) plugins, err := findPlugins(helmpath.Plugins())
if err != nil { if err != nil {
return err return err
} }
@ -69,7 +67,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error {
for _, name := range o.names { for _, name := range o.names {
if found := findPlugin(plugins, name); found != nil { if found := findPlugin(plugins, name); found != nil {
if err := updatePlugin(found, o.home); err != nil { if err := updatePlugin(found); err != nil {
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err)) errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
} else { } else {
fmt.Fprintf(out, "Updated plugin: %s\n", name) fmt.Fprintf(out, "Updated plugin: %s\n", name)
@ -84,7 +82,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error {
return nil return nil
} }
func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { func updatePlugin(p *plugin.Plugin) error {
exactLocation, err := filepath.EvalSymlinks(p.Dir) exactLocation, err := filepath.EvalSymlinks(p.Dir)
if err != nil { if err != nil {
return err return err
@ -94,7 +92,7 @@ func updatePlugin(p *plugin.Plugin, home helmpath.Home) error {
return err return err
} }
i, err := installer.FindSource(absExactLocation, home) i, err := installer.FindSource(absExactLocation)
if err != nil { if err != nil {
return err return err
} }

@ -24,19 +24,27 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestPullCmd(t *testing.T) { func TestPullCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
hh := testHelmHome(t) srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*")
settings.Home = hh if err != nil {
t.Fatal(err)
srv := repotest.NewServer(hh.String()) }
defer srv.Stop() defer srv.Stop()
// all flags will get "--home=TMDIR -d outdir" appended. if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
// all flags will get "-d outdir" appended.
tests := []struct { tests := []struct {
name string name string
args []string args []string
@ -117,19 +125,12 @@ func TestPullCmd(t *testing.T) {
}, },
} }
if _, err := srv.CopyCharts("testdata/testcharts/*.tgz*"); err != nil {
t.Fatal(err)
}
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
for _, tt := range tests { for _, tt := range tests {
outdir := hh.Path("testout") outdir := filepath.Join(helmpath.DataPath(), "testout")
os.RemoveAll(outdir) os.RemoveAll(outdir)
os.Mkdir(outdir, 0755) os.Mkdir(outdir, 0755)
cmd := strings.Join(append(tt.args, "-d", "'"+outdir+"'", "--home", "'"+hh.String()+"'"), " ") cmd := strings.Join(append(tt.args, "-d", "'"+outdir+"'"), " ")
_, out, err := executeActionCommand("fetch " + cmd) _, out, err := executeActionCommand("fetch " + cmd)
if err != nil { if err != nil {
if tt.wantError { if tt.wantError {

@ -34,7 +34,6 @@ type repoAddOptions struct {
url string url string
username string username string
password string password string
home helmpath.Home
noupdate bool noupdate bool
certFile string certFile string
@ -52,7 +51,6 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.url = args[1] o.url = args[1]
o.home = settings.Home
return o.run(out) return o.run(out)
}, },
@ -70,15 +68,15 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
} }
func (o *repoAddOptions) run(out io.Writer) error { func (o *repoAddOptions) run(out io.Writer) error {
if err := addRepository(o.name, o.url, o.username, o.password, o.home, o.certFile, o.keyFile, o.caFile, o.noupdate); err != nil { if err := addRepository(o.name, o.url, o.username, o.password, o.certFile, o.keyFile, o.caFile, o.noupdate); err != nil {
return err return err
} }
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name) fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
return nil return nil
} }
func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { func addRepository(name, url, username, password string, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadFile(home.RepositoryFile()) f, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return err return err
} }
@ -87,10 +85,8 @@ func addRepository(name, url, username, password string, home helmpath.Home, cer
return errors.Errorf("repository name (%s) already exists, please specify a different name", name) return errors.Errorf("repository name (%s) already exists, please specify a different name", name)
} }
cif := home.CacheIndex(name)
c := repo.Entry{ c := repo.Entry{
Name: name, Name: name,
Cache: cif,
URL: url, URL: url,
Username: username, Username: username,
Password: password, Password: password,
@ -104,11 +100,11 @@ func addRepository(name, url, username, password string, home helmpath.Home, cer
return err return err
} }
if err := r.DownloadIndexFile(home.Cache()); err != nil { if err := r.DownloadIndexFile(); err != nil {
return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url) return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
} }
f.Update(&c) f.Update(&c)
return f.WriteFile(home.RepositoryFile(), 0644) return f.WriteFile(helmpath.RepositoryFile(), 0644)
} }

@ -21,6 +21,8 @@ import (
"os" "os"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
@ -28,21 +30,35 @@ import (
func TestRepoAddCmd(t *testing.T) { func TestRepoAddCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
srv, hh, err := repotest.NewTempServer("testdata/testserver/*.*") ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer srv.Stop()
defer func() {
srv.Stop() repoFile := helmpath.RepositoryFile()
os.RemoveAll(hh.String()) if _, err := os.Stat(repoFile); err != nil {
}() rf := repo.NewFile()
ensureTestHome(t, hh) rf.Add(&repo.Entry{
settings.Home = hh Name: "charts",
URL: "http://example.com/foo",
})
if err := rf.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
if r, err := repo.LoadFile(repoFile); err == repo.ErrRepoOutOfDate {
if err := r.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "add a repository", name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s --home '%s'", srv.URL(), hh), cmd: fmt.Sprintf("repo add test-name %s", srv.URL()),
golden: "output/repo-add.txt", golden: "output/repo-add.txt",
}} }}
@ -52,38 +68,52 @@ func TestRepoAddCmd(t *testing.T) {
func TestRepoAdd(t *testing.T) { func TestRepoAdd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*") ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer ts.Stop()
defer func() {
ts.Stop() repoFile := helmpath.RepositoryFile()
os.RemoveAll(hh.String()) if _, err := os.Stat(repoFile); err != nil {
}() rf := repo.NewFile()
ensureTestHome(t, hh) rf.Add(&repo.Entry{
settings.Home = hh Name: "charts",
URL: "http://example.com/foo",
})
if err := rf.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
if r, err := repo.LoadFile(repoFile); err == repo.ErrRepoOutOfDate {
if err := r.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
const testRepoName = "test-name" const testRepoName = "test-name"
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", true); err != nil { if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", true); err != nil {
t.Error(err) t.Error(err)
} }
f, err := repo.LoadFile(hh.RepositoryFile()) f, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if !f.Has(testRepoName) { if !f.Has(testRepoName) {
t.Errorf("%s was not successfully inserted into %s", testRepoName, hh.RepositoryFile()) t.Errorf("%s was not successfully inserted into %s", testRepoName, helmpath.RepositoryFile())
} }
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", false); err != nil { if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", false); err != nil {
t.Errorf("Repository was not updated: %s", err) t.Errorf("Repository was not updated: %s", err)
} }
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", false); err != nil { if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", false); err != nil {
t.Errorf("Duplicate repository name was added") t.Errorf("Duplicate repository name was added")
} }
} }

@ -23,12 +23,13 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
func TestRepoIndexCmd(t *testing.T) { func TestRepoIndexCmd(t *testing.T) {
dir := testTempDir(t) dir := ensure.TempDir(t)
comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") comp := filepath.Join(dir, "compressedchart-0.1.0.tgz")
if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil { if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil {

@ -29,28 +29,13 @@ import (
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
type repoListOptions struct {
home helmpath.Home
}
func newRepoListCmd(out io.Writer) *cobra.Command { func newRepoListCmd(out io.Writer) *cobra.Command {
o := &repoListOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "list chart repositories", Short: "list chart repositories",
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home f, err := repo.LoadFile(helmpath.RepositoryFile())
return o.run(out)
},
}
return cmd
}
func (o *repoListOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.home.RepositoryFile())
if err != nil { if err != nil {
return err return err
} }
@ -64,4 +49,8 @@ func (o *repoListOptions) run(out io.Writer) error {
} }
fmt.Fprintln(out, table) fmt.Fprintln(out, table)
return nil return nil
},
}
return cmd
} }

@ -29,36 +29,22 @@ import (
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
type repoRemoveOptions struct {
name string
home helmpath.Home
}
func newRepoRemoveCmd(out io.Writer) *cobra.Command { func newRepoRemoveCmd(out io.Writer) *cobra.Command {
o := &repoRemoveOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "remove [NAME]", Use: "remove [NAME]",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Short: "remove a chart repository", Short: "remove a chart repository",
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] return removeRepoLine(out, args[0])
o.home = settings.Home
return o.run(out)
}, },
} }
return cmd return cmd
} }
func (r *repoRemoveOptions) run(out io.Writer) error { func removeRepoLine(out io.Writer, name string) error {
return removeRepoLine(out, r.name, r.home) repoFile := helmpath.RepositoryFile()
}
func removeRepoLine(out io.Writer, name string, home helmpath.Home) error {
repoFile := home.RepositoryFile()
r, err := repo.LoadFile(repoFile) r, err := repo.LoadFile(repoFile)
if err != nil { if err != nil {
return err return err
@ -71,7 +57,7 @@ func removeRepoLine(out io.Writer, name string, home helmpath.Home) error {
return err return err
} }
if err := removeRepoCache(name, home); err != nil { if err := removeRepoCache(name); err != nil {
return err return err
} }
@ -80,9 +66,9 @@ func removeRepoLine(out io.Writer, name string, home helmpath.Home) error {
return nil return nil
} }
func removeRepoCache(name string, home helmpath.Home) error { func removeRepoCache(name string) error {
if _, err := os.Stat(home.CacheIndex(name)); err == nil { if _, err := os.Stat(helmpath.CacheIndex(name)); err == nil {
err = os.Remove(home.CacheIndex(name)) err = os.Remove(helmpath.CacheIndex(name))
if err != nil { if err != nil {
return err return err
} }

@ -22,6 +22,8 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
@ -29,44 +31,58 @@ import (
func TestRepoRemove(t *testing.T) { func TestRepoRemove(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*") ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer ts.Stop()
defer func() {
ts.Stop() repoFile := helmpath.RepositoryFile()
os.RemoveAll(hh.String()) if _, err := os.Stat(repoFile); err != nil {
}() rf := repo.NewFile()
ensureTestHome(t, hh) rf.Add(&repo.Entry{
settings.Home = hh Name: "charts",
URL: "http://example.com/foo",
})
if err := rf.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
if r, err := repo.LoadFile(repoFile); err == repo.ErrRepoOutOfDate {
if err := r.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
const testRepoName = "test-name" const testRepoName = "test-name"
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
if err := removeRepoLine(b, testRepoName, hh); err == nil { if err := removeRepoLine(b, testRepoName); err == nil {
t.Errorf("Expected error removing %s, but did not get one.", testRepoName) t.Errorf("Expected error removing %s, but did not get one.", testRepoName)
} }
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", true); err != nil { if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", true); err != nil {
t.Error(err) t.Error(err)
} }
mf, _ := os.Create(hh.CacheIndex(testRepoName)) mf, _ := os.Create(helmpath.CacheIndex(testRepoName))
mf.Close() mf.Close()
b.Reset() b.Reset()
if err := removeRepoLine(b, testRepoName, hh); err != nil { if err := removeRepoLine(b, testRepoName); err != nil {
t.Errorf("Error removing %s from repositories", testRepoName) t.Errorf("Error removing %s from repositories", testRepoName)
} }
if !strings.Contains(b.String(), "has been removed") { if !strings.Contains(b.String(), "has been removed") {
t.Errorf("Unexpected output: %s", b.String()) t.Errorf("Unexpected output: %s", b.String())
} }
if _, err := os.Stat(hh.CacheIndex(testRepoName)); err == nil { if _, err := os.Stat(helmpath.CacheIndex(testRepoName)); err == nil {
t.Errorf("Error cache file was not removed for repository %s", testRepoName) t.Errorf("Error cache file was not removed for repository %s", testRepoName)
} }
f, err := repo.LoadFile(hh.RepositoryFile()) f, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -41,8 +41,7 @@ future releases.
var errNoRepositories = errors.New("no repositories found. You must add one before updating") var errNoRepositories = errors.New("no repositories found. You must add one before updating")
type repoUpdateOptions struct { type repoUpdateOptions struct {
update func([]*repo.ChartRepository, io.Writer, helmpath.Home) update func([]*repo.ChartRepository, io.Writer)
home helmpath.Home
} }
func newRepoUpdateCmd(out io.Writer) *cobra.Command { func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@ -55,7 +54,6 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
Long: updateDesc, Long: updateDesc,
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out) return o.run(out)
}, },
} }
@ -63,7 +61,7 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
} }
func (o *repoUpdateOptions) run(out io.Writer) error { func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.home.RepositoryFile()) f, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return err return err
} }
@ -80,18 +78,18 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
repos = append(repos, r) repos = append(repos, r)
} }
o.update(repos, out, o.home) o.update(repos, out)
return nil return nil
} }
func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Home) { func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
var wg sync.WaitGroup var wg sync.WaitGroup
for _, re := range repos { for _, re := range repos {
wg.Add(1) wg.Add(1)
go func(re *repo.ChartRepository) { go func(re *repo.ChartRepository) {
defer wg.Done() defer wg.Done()
if err := re.DownloadIndexFile(home.Cache()); err != nil { if err := re.DownloadIndexFile(); err != nil {
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err) fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
} else { } else {
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)

@ -23,8 +23,10 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
@ -32,20 +34,36 @@ import (
func TestUpdateCmd(t *testing.T) { func TestUpdateCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
hh := testHelmHome(t) ensure.HelmHome(t)
settings.Home = hh defer ensure.CleanHomeDirs(t)
repoFile := helmpath.RepositoryFile()
if _, err := os.Stat(repoFile); err != nil {
rf := repo.NewFile()
rf.Add(&repo.Entry{
Name: "charts",
URL: "http://example.com/foo",
})
if err := rf.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
if r, err := repo.LoadFile(repoFile); err == repo.ErrRepoOutOfDate {
if err := r.WriteFile(repoFile, 0644); err != nil {
t.Fatal(err)
}
}
out := bytes.NewBuffer(nil) out := bytes.NewBuffer(nil)
// Instead of using the HTTP updater, we provide our own for this test. // Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently. // The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, hh helmpath.Home) { updater := func(repos []*repo.ChartRepository, out io.Writer) {
for _, re := range repos { for _, re := range repos {
fmt.Fprintln(out, re.Config.Name) fmt.Fprintln(out, re.Config.Name)
} }
} }
o := &repoUpdateOptions{ o := &repoUpdateOptions{
update: updater, update: updater,
home: hh,
} }
if err := o.run(out); err != nil { if err := o.run(out); err != nil {
t.Fatal(err) t.Fatal(err)
@ -59,29 +77,25 @@ func TestUpdateCmd(t *testing.T) {
func TestUpdateCharts(t *testing.T) { func TestUpdateCharts(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*") ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer ts.Stop()
defer func() {
ts.Stop()
os.RemoveAll(hh.String())
}()
ensureTestHome(t, hh)
settings.Home = hh
r, err := repo.NewChartRepository(&repo.Entry{ r, err := repo.NewChartRepository(&repo.Entry{
Name: "charts", Name: "charts",
URL: ts.URL(), URL: ts.URL(),
Cache: hh.CacheIndex("charts"),
}, getter.All(settings)) }, getter.All(settings))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
updateCharts([]*repo.ChartRepository{r}, b, hh) updateCharts([]*repo.ChartRepository{r}, b)
got := b.String() got := b.String()
if strings.Contains(got, "Unable to get an update") { if strings.Contains(got, "Unable to get an update") {

@ -26,6 +26,7 @@ import (
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/registry" "helm.sh/helm/pkg/registry"
) )
@ -100,7 +101,9 @@ Common actions from this point include:
- helm list: list releases of charts - helm list: list releases of charts
Environment: Environment:
$HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm $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_DRIVER set the backend storage driver. Values are: configmap, secret, memory $HELM_DRIVER set the backend storage driver. Values are: configmap, secret, memory
$HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
@ -127,7 +130,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Add the registry client based on settings // Add the registry client based on settings
// TODO: Move this elsewhere (first, settings.Init() must move) // TODO: Move this elsewhere (first, settings.Init() must move)
// TODO: handle errors, dont panic // TODO: handle errors, dont panic
credentialsFile := filepath.Join(settings.Home.Registry(), registry.CredentialsFileBasename) credentialsFile := filepath.Join(helmpath.Registry(), registry.CredentialsFileBasename)
client, err := auth.NewClient(credentialsFile) client, err := auth.NewClient(credentialsFile)
if err != nil { if err != nil {
panic(err) panic(err)
@ -145,7 +148,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
Resolver: registry.Resolver{ Resolver: registry.Resolver{
Resolver: resolver, Resolver: resolver,
}, },
CacheRootDir: settings.Home.Registry(), CacheRootDir: helmpath.Registry(),
}) })
cmd.AddCommand( cmd.AddCommand(
@ -177,7 +180,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newUpgradeCmd(actionConfig, out), newUpgradeCmd(actionConfig, out),
newCompletionCmd(out), newCompletionCmd(out),
newHomeCmd(out), newPathCmd(out),
newInitCmd(out), newInitCmd(out),
newPluginCmd(out), newPluginCmd(out),
newVersionCmd(out), newVersionCmd(out),

@ -21,74 +21,77 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"k8s.io/client-go/util/homedir" "helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestRootCmd(t *testing.T) { func TestRootCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
tests := []struct { tests := []struct {
name, args, home string name, args, cachePath, configPath, dataPath string
envars map[string]string envars map[string]string
}{ }{
{ {
name: "defaults", name: "defaults",
args: "home", args: "home",
home: filepath.Join(homedir.HomeDir(), ".helm"),
}, },
{ {
name: "with --home set", name: "with $XDG_CACHE_HOME set",
args: "--home /foo",
home: "/foo",
},
{
name: "subcommands with --home set",
args: "home --home /foo",
home: "/foo",
},
{
name: "with $HELM_HOME set",
args: "home", args: "home",
envars: map[string]string{"HELM_HOME": "/bar"}, envars: map[string]string{xdg.CacheHomeEnvVar: "/bar"},
home: "/bar", cachePath: "/bar/helm",
}, },
{ {
name: "subcommands with $HELM_HOME set", name: "with $XDG_CONFIG_HOME set",
args: "home", args: "home",
envars: map[string]string{"HELM_HOME": "/bar"}, envars: map[string]string{xdg.ConfigHomeEnvVar: "/bar"},
home: "/bar", configPath: "/bar/helm",
}, },
{ {
name: "with $HELM_HOME and --home set", name: "with $XDG_DATA_HOME set",
args: "home --home /foo", args: "home",
envars: map[string]string{"HELM_HOME": "/bar"}, envars: map[string]string{xdg.DataHomeEnvVar: "/bar"},
home: "/foo", dataPath: "/bar/helm",
}, },
} }
// ensure not set locally
os.Unsetenv("HELM_HOME")
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
defer os.Unsetenv("HELM_HOME") ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
for k, v := range tt.envars { for k, v := range tt.envars {
os.Setenv(k, v) os.Setenv(k, v)
} }
cmd, _, err := executeActionCommand(tt.args) if _, _, err := executeActionCommand(tt.args); err != nil {
if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
if settings.Home.String() != tt.home { // NOTE(bacongobbler): we need to check here after calling ensure.HelmHome so we
t.Errorf("expected home %q, got %q", tt.home, settings.Home) // load the proper paths after XDG_*_HOME is set
if tt.cachePath == "" {
tt.cachePath = filepath.Join(os.Getenv(xdg.CacheHomeEnvVar), "helm")
}
if tt.configPath == "" {
tt.configPath = filepath.Join(os.Getenv(xdg.ConfigHomeEnvVar), "helm")
}
if tt.dataPath == "" {
tt.dataPath = filepath.Join(os.Getenv(xdg.DataHomeEnvVar), "helm")
}
if helmpath.CachePath() != tt.cachePath {
t.Errorf("expected cache path %q, got %q", tt.cachePath, helmpath.CachePath())
}
if helmpath.ConfigPath() != tt.configPath {
t.Errorf("expected config path %q, got %q", tt.configPath, helmpath.ConfigPath())
} }
homeFlag := cmd.Flag("home").Value.String() if helmpath.DataPath() != tt.dataPath {
homeFlag = os.ExpandEnv(homeFlag) t.Errorf("expected data path %q, got %q", tt.dataPath, helmpath.DataPath())
if homeFlag != tt.home {
t.Errorf("expected home %q, got %q", tt.home, homeFlag)
} }
}) })
} }

@ -42,8 +42,6 @@ Repositories are managed with 'helm repo' commands.
const searchMaxScore = 25 const searchMaxScore = 25
type searchOptions struct { type searchOptions struct {
helmhome helmpath.Home
versions bool versions bool
regexp bool regexp bool
version string version string
@ -57,7 +55,6 @@ func newSearchCmd(out io.Writer) *cobra.Command {
Short: "search for a keyword in charts", Short: "search for a keyword in charts",
Long: searchDesc, Long: searchDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.helmhome = settings.Home
return o.run(out, args) return o.run(out, args)
}, },
} }
@ -141,7 +138,7 @@ func (o *searchOptions) formatSearchResults(res []*search.Result) string {
func (o *searchOptions) buildIndex(out io.Writer) (*search.Index, error) { func (o *searchOptions) buildIndex(out io.Writer) (*search.Index, error) {
// Load the repositories.yaml // Load the repositories.yaml
rf, err := repo.LoadFile(o.helmhome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -149,7 +146,7 @@ func (o *searchOptions) buildIndex(out io.Writer) (*search.Index, error) {
i := search.NewIndex() i := search.NewIndex()
for _, re := range rf.Repositories { for _, re := range rf.Repositories {
n := re.Name n := re.Name
f := o.helmhome.CacheIndex(n) f := helmpath.CacheIndex(n)
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
// TODO should print to stderr // TODO should print to stderr

@ -17,55 +17,58 @@ limitations under the License.
package main package main
import ( import (
"os"
"testing" "testing"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestSearchCmd(t *testing.T) { func TestSearchCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
setHome := func(cmd string) string { os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
return cmd + " --home=testdata/helmhome" os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
} os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "search for 'maria', expect one match", name: "search for 'maria', expect one match",
cmd: setHome("search maria"), cmd: "search maria",
golden: "output/search-single.txt", golden: "output/search-single.txt",
}, { }, {
name: "search for 'alpine', expect two matches", name: "search for 'alpine', expect two matches",
cmd: setHome("search alpine"), cmd: "search alpine",
golden: "output/search-multiple.txt", golden: "output/search-multiple.txt",
}, { }, {
name: "search for 'alpine' with versions, expect three matches", name: "search for 'alpine' with versions, expect three matches",
cmd: setHome("search alpine --versions"), cmd: "search alpine --versions",
golden: "output/search-multiple-versions.txt", golden: "output/search-multiple-versions.txt",
}, { }, {
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
cmd: setHome("search alpine --version '>= 0.1, < 0.2'"), cmd: "search alpine --version '>= 0.1, < 0.2'",
golden: "output/search-constraint.txt", golden: "output/search-constraint.txt",
}, { }, {
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
cmd: setHome("search alpine --versions --version '>= 0.1, < 0.2'"), cmd: "search alpine --versions --version '>= 0.1, < 0.2'",
golden: "output/search-versions-constraint.txt", golden: "output/search-versions-constraint.txt",
}, { }, {
name: "search for 'alpine' with version constraint, expect one match with version 0.2.0", name: "search for 'alpine' with version constraint, expect one match with version 0.2.0",
cmd: setHome("search alpine --version '>= 0.1'"), cmd: "search alpine --version '>= 0.1'",
golden: "output/search-constraint-single.txt", golden: "output/search-constraint-single.txt",
}, { }, {
name: "search for 'alpine' with version constraint and --versions, expect two matches", name: "search for 'alpine' with version constraint and --versions, expect two matches",
cmd: setHome("search alpine --versions --version '>= 0.1'"), cmd: "search alpine --versions --version '>= 0.1'",
golden: "output/search-multiple-versions-constraints.txt", golden: "output/search-multiple-versions-constraints.txt",
}, { }, {
name: "search for 'syzygy', expect no matches", name: "search for 'syzygy', expect no matches",
cmd: setHome("search syzygy"), cmd: "search syzygy",
golden: "output/search-not-found.txt", golden: "output/search-not-found.txt",
}, { }, {
name: "search for 'alp[a-z]+', expect two matches", name: "search for 'alp[a-z]+', expect two matches",
cmd: setHome("search alp[a-z]+ --regexp"), cmd: "search alp[a-z]+ --regexp",
golden: "output/search-regex.txt", golden: "output/search-regex.txt",
}, { }, {
name: "search for 'alp[', expect failure to compile regexp", name: "search for 'alp[', expect failure to compile regexp",
cmd: setHome("search alp[ --regexp"), cmd: "search alp[ --regexp",
wantError: true, wantError: true,
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)

@ -1,4 +1,4 @@
name: env name: env
usage: "env stuff" usage: "env stuff"
description: "show the env" description: "show the env"
command: "echo $HELM_HOME" command: "echo $HELM_PATH_CONFIG"

@ -2,8 +2,9 @@
echo $HELM_PLUGIN_NAME echo $HELM_PLUGIN_NAME
echo $HELM_PLUGIN_DIR echo $HELM_PLUGIN_DIR
echo $HELM_PLUGIN echo $HELM_PLUGIN
echo $HELM_HOME
echo $HELM_PATH_REPOSITORY
echo $HELM_PATH_REPOSITORY_FILE
echo $HELM_PATH_CACHE echo $HELM_PATH_CACHE
echo $HELM_PATH_CONFIG
echo $HELM_PATH_DATA
echo $HELM_PATH_REPOSITORY_FILE
echo $HELM_PATH_REPOSITORY_CACHE
echo $HELM_BIN echo $HELM_BIN

@ -23,6 +23,7 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
@ -30,7 +31,7 @@ import (
) )
func TestUpgradeCmd(t *testing.T) { func TestUpgradeCmd(t *testing.T) {
tmpChart := testTempDir(t) tmpChart := ensure.TempDir(t)
cfile := &chart.Chart{ cfile := &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV1, APIVersion: chart.APIVersionV1,
@ -224,7 +225,7 @@ func TestUpgradeWithValuesFile(t *testing.T) {
} }
func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
tmpChart := testTempDir(t) tmpChart := ensure.TempDir(t)
configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil { if err != nil {
t.Fatalf("Error loading template yaml %v", err) t.Fatalf("Error loading template yaml %v", err)

@ -0,0 +1,84 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ensure
import (
"io/ioutil"
"os"
"testing"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
)
// HelmHome sets up a Helm Home in a temp dir.
func HelmHome(t *testing.T) {
t.Helper()
cachePath := TempDir(t)
configPath := TempDir(t)
dataPath := TempDir(t)
os.Setenv(xdg.CacheHomeEnvVar, cachePath)
os.Setenv(xdg.ConfigHomeEnvVar, configPath)
os.Setenv(xdg.DataHomeEnvVar, dataPath)
HomeDirs(t)
}
// HomeDirs creates a home directory like ensureHome, but without remote references.
func HomeDirs(t *testing.T) {
t.Helper()
for _, p := range []string{
helmpath.CachePath(),
helmpath.ConfigPath(),
helmpath.DataPath(),
helmpath.RepositoryCache(),
helmpath.Plugins(),
helmpath.PluginCache(),
helmpath.Starters(),
} {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatal(err)
}
}
}
// CleanHomeDirs removes the directories created by HomeDirs.
func CleanHomeDirs(t *testing.T) {
t.Helper()
for _, p := range []string{
helmpath.CachePath(),
helmpath.ConfigPath(),
helmpath.DataPath(),
helmpath.RepositoryCache(),
helmpath.Plugins(),
helmpath.PluginCache(),
helmpath.Starters(),
} {
if err := os.RemoveAll(p); err != nil {
t.Log(err)
}
}
}
// TempDir ensures a scratch test directory for unit testing purposes.
func TempDir(t *testing.T) string {
t.Helper()
d, err := ioutil.TempDir("", "helm")
if err != nil {
t.Fatal(err)
}
return d
}

@ -38,6 +38,7 @@ import (
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/hooks" "helm.sh/helm/pkg/hooks"
kubefake "helm.sh/helm/pkg/kube/fake" kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
@ -574,7 +575,6 @@ OUTER:
// Order of resolution: // Order of resolution:
// - relative to current working directory // - relative to current working directory
// - if path is absolute or begins with '.', error out here // - if path is absolute or begins with '.', error out here
// - chart repos in $HELM_HOME
// - URL // - URL
// //
// If 'verify' is true, this will attempt to also verify the chart. // If 'verify' is true, this will attempt to also verify the chart.
@ -598,13 +598,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
return name, errors.Errorf("path %q not found", name) return name, errors.Errorf("path %q not found", name)
} }
crepo := filepath.Join(settings.Home.Repository(), name)
if _, err := os.Stat(crepo); err == nil {
return filepath.Abs(crepo)
}
dl := downloader.ChartDownloader{ dl := downloader.ChartDownloader{
HelmHome: settings.Home,
Out: os.Stdout, Out: os.Stdout,
Keyring: c.Keyring, Keyring: c.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
@ -624,11 +618,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
name = chartURL name = chartURL
} }
if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { if _, err := os.Stat(helmpath.Archive()); os.IsNotExist(err) {
os.MkdirAll(settings.Home.Archive(), 0744) os.MkdirAll(helmpath.Archive(), 0744)
} }
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) filename, _, err := dl.DownloadTo(name, version, helmpath.Archive())
if err == nil { if err == nil {
lname, err := filepath.Abs(filename) lname, err := filepath.Abs(filename)
if err != nil { if err != nil {

@ -57,7 +57,6 @@ func (p *Pull) Run(chartRef string) (string, error) {
var out strings.Builder var out strings.Builder
c := downloader.ChartDownloader{ c := downloader.ChartDownloader{
HelmHome: p.Settings.Home,
Out: &out, Out: &out,
Keyring: p.Keyring, Keyring: p.Keyring,
Verify: downloader.VerifyNever, Verify: downloader.VerifyNever,

@ -24,21 +24,12 @@ package cli
import ( import (
"os" "os"
"path/filepath"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/client-go/util/homedir"
"helm.sh/helm/pkg/helmpath"
) )
// defaultHelmHome is the default HELM_HOME.
var defaultHelmHome = filepath.Join(homedir.HomeDir(), ".helm")
// EnvSettings describes all of the environment settings. // EnvSettings describes all of the environment settings.
type EnvSettings struct { type EnvSettings struct {
// Home is the local path to the Helm home directory.
Home helmpath.Home
// Namespace is the namespace scope. // Namespace is the namespace scope.
Namespace string Namespace string
// KubeConfig is the path to the kubeconfig file. // KubeConfig is the path to the kubeconfig file.
@ -51,7 +42,6 @@ type EnvSettings struct {
// AddFlags binds flags to the given flagset. // AddFlags binds flags to the given flagset.
func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVar((*string)(&s.Home), "home", defaultHelmHome, "location of your Helm config. Overrides $HELM_HOME")
fs.StringVarP(&s.Namespace, "namespace", "n", "", "namespace scope for this request") fs.StringVarP(&s.Namespace, "namespace", "n", "", "namespace scope for this request")
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use") fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use")
@ -65,18 +55,9 @@ func (s *EnvSettings) Init(fs *pflag.FlagSet) {
} }
} }
// PluginDirs is the path to the plugin directories.
func (s EnvSettings) PluginDirs() string {
if d, ok := os.LookupEnv("HELM_PLUGIN"); ok {
return d
}
return s.Home.Plugins()
}
// envMap maps flag names to envvars // envMap maps flag names to envvars
var envMap = map[string]string{ var envMap = map[string]string{
"debug": "HELM_DEBUG", "debug": "HELM_DEBUG",
"home": "HELM_HOME",
"namespace": "HELM_NAMESPACE", "namespace": "HELM_NAMESPACE",
} }

@ -22,8 +22,6 @@ import (
"testing" "testing"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"helm.sh/helm/pkg/helmpath"
) )
func TestEnvSettings(t *testing.T) { func TestEnvSettings(t *testing.T) {
@ -35,37 +33,29 @@ func TestEnvSettings(t *testing.T) {
envars map[string]string envars map[string]string
// expected values // expected values
home, ns, kcontext, plugins string ns, kcontext string
debug bool debug bool
}{ }{
{ {
name: "defaults", name: "defaults",
home: defaultHelmHome,
plugins: helmpath.Home(defaultHelmHome).Plugins(),
ns: "", ns: "",
}, },
{ {
name: "with flags set", name: "with flags set",
args: "--home /foo --debug --namespace=myns", args: "--debug --namespace=myns",
home: "/foo",
plugins: helmpath.Home("/foo").Plugins(),
ns: "myns", ns: "myns",
debug: true, debug: true,
}, },
{ {
name: "with envvars set", name: "with envvars set",
envars: map[string]string{"HELM_HOME": "/bar", "HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, envars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"},
home: "/bar",
plugins: helmpath.Home("/bar").Plugins(),
ns: "yourns", ns: "yourns",
debug: true, debug: true,
}, },
{ {
name: "with flags and envvars set", name: "with flags and envvars set",
args: "--home /foo --debug --namespace=myns", args: "--debug --namespace=myns",
envars: map[string]string{"HELM_HOME": "/bar", "HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_PLUGIN": "glade"}, envars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"},
home: "/foo",
plugins: "glade",
ns: "myns", ns: "myns",
debug: true, debug: true,
}, },
@ -87,12 +77,6 @@ func TestEnvSettings(t *testing.T) {
settings.Init(flags) settings.Init(flags)
if settings.Home != helmpath.Home(tt.home) {
t.Errorf("expected home %q, got %q", tt.home, settings.Home)
}
if settings.PluginDirs() != tt.plugins {
t.Errorf("expected plugins %q, got %q", tt.plugins, settings.PluginDirs())
}
if settings.Debug != tt.debug { if settings.Debug != tt.debug {
t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug)
} }

@ -64,8 +64,6 @@ type ChartDownloader struct {
Verify VerificationStrategy Verify VerificationStrategy
// Keyring is the keyring file used for verification. // Keyring is the keyring file used for verification.
Keyring string Keyring string
// HelmHome is the $HELM_HOME.
HelmHome helmpath.Home
// Getter collection for the operation // Getter collection for the operation
Getters getter.Providers Getters getter.Providers
// Options provide parameters to be passed along to the Getter being initialized. // Options provide parameters to be passed along to the Getter being initialized.
@ -159,7 +157,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
c.Options = append(c.Options, getter.WithURL(ref)) c.Options = append(c.Options, getter.WithURL(ref))
rf, err := repo.LoadFile(c.HelmHome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return u, err return u, err
} }
@ -220,7 +218,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
// Next, we need to load the index, and actually look up the chart. // Next, we need to load the index, and actually look up the chart.
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name))
if err != nil { if err != nil {
return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
} }
@ -339,7 +337,7 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry,
return nil, err return nil, err
} }
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name))
if err != nil { if err != nil {
return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
} }

@ -16,20 +16,24 @@ limitations under the License.
package downloader package downloader
import ( import (
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestResolveChartRef(t *testing.T) { func TestResolveChartRef(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
tests := []struct { tests := []struct {
name, ref, expect, version string name, ref, expect, version string
fail bool fail bool
@ -52,7 +56,6 @@ func TestResolveChartRef(t *testing.T) {
} }
c := ChartDownloader{ c := ChartDownloader{
HelmHome: helmpath.Home("testdata/helmhome"),
Out: os.Stderr, Out: os.Stderr,
Getters: getter.All(cli.EnvSettings{}), Getters: getter.All(cli.EnvSettings{}),
} }
@ -102,97 +105,17 @@ func TestIsTar(t *testing.T) {
} }
func TestDownloadTo(t *testing.T) { func TestDownloadTo(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-downloadto-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err) dest := helmpath.CachePath()
}
defer os.RemoveAll(tmp)
hh := helmpath.Home(tmp) // Set up a fake repo with basic auth enabled
dest := filepath.Join(hh.String(), "dest") srv := repotest.NewServer(helmpath.CachePath())
configDirectories := []string{ srv.Stop()
hh.String(),
hh.Repository(),
hh.Cache(),
dest,
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatalf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
t.Fatalf("%s must be a directory", p)
}
}
// Set up a fake repo
srv := repotest.NewServer(tmp)
defer srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil {
t.Error(err) t.Error(err)
return return
} }
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
c := ChartDownloader{
HelmHome: hh,
Out: os.Stderr,
Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub",
Getters: getter.All(cli.EnvSettings{}),
}
cname := "/signtest-0.1.0.tgz"
where, v, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil {
t.Error(err)
return
}
if expect := filepath.Join(dest, cname); where != expect {
t.Errorf("Expected download to %s, got %s", expect, where)
}
if v.FileHash == "" {
t.Error("File hash was empty, but verification is required.")
}
if _, err := os.Stat(filepath.Join(dest, cname)); err != nil {
t.Error(err)
return
}
}
func TestDownloadTo_WithOptions(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-downloadto-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
hh := helmpath.Home(tmp)
dest := filepath.Join(hh.String(), "dest")
configDirectories := []string{
hh.String(),
hh.Repository(),
hh.Cache(),
dest,
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatalf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
t.Fatalf("%s must be a directory", p)
}
}
// Set up a fake repo with basic auth enabled
srv := repotest.NewServer(tmp)
srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" { if !ok || username != "username" || password != "password" {
@ -201,16 +124,15 @@ func TestDownloadTo_WithOptions(t *testing.T) {
})) }))
srv.Start() srv.Start()
defer srv.Stop() defer srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { if err := srv.CreateIndex(); err != nil {
t.Error(err) t.Fatal(err)
return
} }
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
c := ChartDownloader{ c := ChartDownloader{
HelmHome: hh,
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyAlways, Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub", Keyring: "testdata/helm-test-key.pub",
@ -222,8 +144,7 @@ func TestDownloadTo_WithOptions(t *testing.T) {
cname := "/signtest-0.1.0.tgz" cname := "/signtest-0.1.0.tgz"
where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) where, v, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
if expect := filepath.Join(dest, cname); where != expect { if expect := filepath.Join(dest, cname); where != expect {
@ -236,48 +157,26 @@ func TestDownloadTo_WithOptions(t *testing.T) {
if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { if _, err := os.Stat(filepath.Join(dest, cname)); err != nil {
t.Error(err) t.Error(err)
return
} }
} }
func TestDownloadTo_VerifyLater(t *testing.T) { func TestDownloadTo_VerifyLater(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-downloadto-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(tmp)
hh := helmpath.Home(tmp) dest := helmpath.CachePath()
dest := filepath.Join(hh.String(), "dest")
configDirectories := []string{
hh.String(),
hh.Repository(),
hh.Cache(),
dest,
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {
t.Fatalf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
t.Fatalf("%s must be a directory", p)
}
}
// Set up a fake repo // Set up a fake repo
srv := repotest.NewServer(tmp) srv, err := repotest.NewTempServer("testdata/*.tgz*")
defer srv.Stop() if err != nil {
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { t.Fatal(err)
t.Error(err)
return
} }
defer srv.Stop()
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
c := ChartDownloader{ c := ChartDownloader{
HelmHome: hh,
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyLater, Verify: VerifyLater,
Getters: getter.All(cli.EnvSettings{}), Getters: getter.All(cli.EnvSettings{}),
@ -285,8 +184,7 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
cname := "/signtest-0.1.0.tgz" cname := "/signtest-0.1.0.tgz"
where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) where, _, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
if expect := filepath.Join(dest, cname); where != expect { if expect := filepath.Join(dest, cname); where != expect {
@ -294,26 +192,25 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
} }
if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { if _, err := os.Stat(filepath.Join(dest, cname)); err != nil {
t.Error(err) t.Fatal(err)
return
} }
if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil {
t.Error(err) t.Fatal(err)
return
} }
} }
func TestScanReposForURL(t *testing.T) { func TestScanReposForURL(t *testing.T) {
hh := helmpath.Home("testdata/helmhome") os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
c := ChartDownloader{ c := ChartDownloader{
HelmHome: hh,
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyLater, Verify: VerifyLater,
Getters: getter.All(cli.EnvSettings{}), Getters: getter.All(cli.EnvSettings{}),
} }
u := "http://example.com/alpine-0.2.0.tgz" u := "http://example.com/alpine-0.2.0.tgz"
rf, err := repo.LoadFile(c.HelmHome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -16,8 +16,8 @@ limitations under the License.
/*Package downloader provides a library for downloading charts. /*Package downloader provides a library for downloading charts.
This package contains various tools for downloading charts from repository This package contains various tools for downloading charts from repository
servers, and then storing them in Helm-specific directory structures (like servers, and then storing them in Helm-specific directory structures. This
HELM_HOME). This library contains many functions that depend on a specific library contains many functions that depend on a specific
filesystem layout. filesystem layout.
*/ */
package downloader package downloader

@ -46,8 +46,6 @@ type Manager struct {
Out io.Writer Out io.Writer
// ChartPath is the path to the unpacked base chart upon which this operates. // ChartPath is the path to the unpacked base chart upon which this operates.
ChartPath string ChartPath string
// HelmHome is the $HELM_HOME directory
HelmHome helmpath.Home
// Verification indicates whether the chart should be verified. // Verification indicates whether the chart should be verified.
Verify VerificationStrategy Verify VerificationStrategy
// Debug is the global "--debug" flag // Debug is the global "--debug" flag
@ -170,7 +168,7 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
// //
// This returns a lock file, which has all of the dependencies normalized to a specific version. // This returns a lock file, which has all of the dependencies normalized to a specific version.
func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
res := resolver.New(m.ChartPath, m.HelmHome) res := resolver.New(m.ChartPath)
return res.Resolve(req, repoNames) return res.Resolve(req, repoNames)
} }
@ -234,7 +232,6 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Out: m.Out, Out: m.Out,
Verify: m.Verify, Verify: m.Verify,
Keyring: m.Keyring, Keyring: m.Keyring,
HelmHome: m.HelmHome,
Getters: m.Getters, Getters: m.Getters,
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(username, password), getter.WithBasicAuth(username, password),
@ -314,7 +311,7 @@ func (m *Manager) safeDeleteDep(name, dir string) error {
// hasAllRepos ensures that all of the referenced deps are in the local repo cache. // hasAllRepos ensures that all of the referenced deps are in the local repo cache.
func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { func (m *Manager) hasAllRepos(deps []*chart.Dependency) error {
rf, err := repo.LoadFile(m.HelmHome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return err return err
} }
@ -348,7 +345,7 @@ Loop:
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file. // getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file.
func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) { func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) {
rf, err := repo.LoadFile(m.HelmHome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -415,7 +412,7 @@ repository, use "https://charts.example.com/" or "@example" instead of
// UpdateRepositories updates all of the local repos to the latest. // UpdateRepositories updates all of the local repos to the latest.
func (m *Manager) UpdateRepositories() error { func (m *Manager) UpdateRepositories() error {
rf, err := repo.LoadFile(m.HelmHome.RepositoryFile()) rf, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil { if err != nil {
return err return err
} }
@ -440,7 +437,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
} }
wg.Add(1) wg.Add(1)
go func(r *repo.ChartRepository) { go func(r *repo.ChartRepository) {
if err := r.DownloadIndexFile(m.HelmHome.Cache()); err != nil { if err := r.DownloadIndexFile(); err != nil {
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err)
} else { } else {
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", r.Config.Name)
@ -552,7 +549,7 @@ func normalizeURL(baseURL, urlOrPath string) (string, error) {
// The key is the local name (which is only present in the repositories.yaml). // The key is the local name (which is only present in the repositories.yaml).
func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) {
indices := map[string]*repo.ChartRepository{} indices := map[string]*repo.ChartRepository{}
repoyaml := m.HelmHome.RepositoryFile() repoyaml := helmpath.RepositoryFile()
// Load repositories.yaml file // Load repositories.yaml file
rf, err := repo.LoadFile(repoyaml) rf, err := repo.LoadFile(repoyaml)
@ -562,8 +559,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
for _, re := range rf.Repositories { for _, re := range rf.Repositories {
lname := re.Name lname := re.Name
cacheindex := m.HelmHome.CacheIndex(lname) index, err := repo.LoadIndexFile(helmpath.CacheIndex(lname))
index, err := repo.LoadIndexFile(cacheindex)
if err != nil { if err != nil {
return indices, err return indices, err
} }

@ -17,11 +17,12 @@ package downloader
import ( import (
"bytes" "bytes"
"os"
"reflect" "reflect"
"testing" "testing"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath/xdg"
) )
func TestVersionEquals(t *testing.T) { func TestVersionEquals(t *testing.T) {
@ -63,10 +64,12 @@ func TestNormalizeURL(t *testing.T) {
} }
func TestFindChartURL(t *testing.T) { func TestFindChartURL(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
m := &Manager{ m := &Manager{
Out: b, Out: b,
HelmHome: helmpath.Home("testdata/helmhome"),
} }
repos, err := m.loadChartRepositories() repos, err := m.loadChartRepositories()
if err != nil { if err != nil {
@ -93,10 +96,12 @@ func TestFindChartURL(t *testing.T) {
} }
func TestGetRepoNames(t *testing.T) { func TestGetRepoNames(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
m := &Manager{ m := &Manager{
Out: b, Out: b,
HelmHome: helmpath.Home("testdata/helmhome"),
} }
tests := []struct { tests := []struct {
name string name string

@ -18,6 +18,9 @@ package getter
import ( import (
"os" "os"
"testing" "testing"
"helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestProvider(t *testing.T) { func TestProvider(t *testing.T) {
@ -50,13 +53,9 @@ func TestProviders(t *testing.T) {
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
oldhh := os.Getenv("HELM_HOME") os.Setenv(xdg.DataHomeEnvVar, "testdata")
defer os.Setenv("HELM_HOME", oldhh)
os.Setenv("HELM_HOME", "")
env := hh(false)
all := All(env) all := All(cli.EnvSettings{})
if len(all) != 3 { if len(all) != 3 {
t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all)) t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all))
} }
@ -67,15 +66,12 @@ func TestAll(t *testing.T) {
} }
func TestByScheme(t *testing.T) { func TestByScheme(t *testing.T) {
oldhh := os.Getenv("HELM_HOME") os.Setenv(xdg.DataHomeEnvVar, "testdata")
defer os.Setenv("HELM_HOME", oldhh)
os.Setenv("HELM_HOME", "")
env := hh(false) if _, err := ByScheme("test", cli.EnvSettings{}); err != nil {
if _, err := ByScheme("test", env); err != nil {
t.Error(err) t.Error(err)
} }
if _, err := ByScheme("https", env); err != nil { if _, err := ByScheme("https", cli.EnvSettings{}); err != nil {
t.Error(err) t.Error(err)
} }
} }

@ -25,13 +25,14 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
// collectPlugins scans for getter plugins. // collectPlugins scans for getter plugins.
// This will load plugins according to the cli. // This will load plugins according to the cli.
func collectPlugins(settings cli.EnvSettings) (Providers, error) { func collectPlugins(settings cli.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(settings.PluginDirs()) plugins, err := plugin.FindPlugins(helmpath.Plugins())
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -17,34 +17,18 @@ package getter
import ( import (
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath/xdg"
) )
func hh(debug bool) cli.EnvSettings {
apath, err := filepath.Abs("./testdata")
if err != nil {
panic(err)
}
hp := helmpath.Home(apath)
return cli.EnvSettings{
Home: hp,
Debug: debug,
}
}
func TestCollectPlugins(t *testing.T) { func TestCollectPlugins(t *testing.T) {
// Reset HELM HOME to testdata. os.Setenv(xdg.DataHomeEnvVar, "testdata")
oldhh := os.Getenv("HELM_HOME")
defer os.Setenv("HELM_HOME", oldhh)
os.Setenv("HELM_HOME", "")
env := hh(false) env := cli.EnvSettings{}
p, err := collectPlugins(env) p, err := collectPlugins(env)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -72,11 +56,9 @@ func TestPluginGetter(t *testing.T) {
t.Skip("TODO: refactor this test to work on windows") t.Skip("TODO: refactor this test to work on windows")
} }
oldhh := os.Getenv("HELM_HOME") os.Setenv(xdg.DataHomeEnvVar, "testdata")
defer os.Setenv("HELM_HOME", oldhh)
os.Setenv("HELM_HOME", "")
env := hh(false) env := cli.EnvSettings{}
pg := NewPluginGetter("echo", env, "test", ".") pg := NewPluginGetter("echo", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {
@ -104,7 +86,7 @@ func TestPluginSubCommands(t *testing.T) {
defer os.Setenv("HELM_HOME", oldhh) defer os.Setenv("HELM_HOME", oldhh)
os.Setenv("HELM_HOME", "") os.Setenv("HELM_HOME", "")
env := hh(false) env := cli.EnvSettings{}
pg := NewPluginGetter("echo -n", env, "test", ".") pg := NewPluginGetter("echo -n", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {

@ -1,82 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helmpath
import (
"fmt"
"os"
"path/filepath"
)
// Home describes the location of a CLI configuration.
//
// This helper builds paths relative to a Helm Home directory.
type Home string
// String returns Home as a string.
//
// Implements fmt.Stringer.
func (h Home) String() string {
return os.ExpandEnv(string(h))
}
// Path returns Home with elements appended.
func (h Home) Path(elem ...string) string {
p := []string{h.String()}
p = append(p, elem...)
return filepath.Join(p...)
}
// Registry returns the path to the local registry cache.
func (h Home) Registry() string {
return h.Path("registry")
}
// Repository returns the path to the local repository.
func (h Home) Repository() string {
return h.Path("repository")
}
// RepositoryFile returns the path to the repositories.yaml file.
func (h Home) RepositoryFile() string {
return h.Path("repository", "repositories.yaml")
}
// Cache returns the path to the local cache.
func (h Home) Cache() string {
return h.Path("repository", "cache")
}
// CacheIndex returns the path to an index for the given named repository.
func (h Home) CacheIndex(name string) string {
target := fmt.Sprintf("%s-index.yaml", name)
return h.Path("repository", "cache", target)
}
// Starters returns the path to the Helm starter packs.
func (h Home) Starters() string {
return h.Path("starters")
}
// Plugins returns the path to the plugins directory.
func (h Home) Plugins() string {
return h.Path("plugins")
}
// Archive returns the path to download chart archives.
func (h Home) Archive() string {
return h.Path("cache", "archive")
}

@ -1,47 +0,0 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
// +build !windows
package helmpath
import (
"runtime"
"testing"
)
func TestHelmHome(t *testing.T) {
hh := Home("/r/users/helmtest")
isEq := func(t *testing.T, a, b string) {
if a != b {
t.Error(runtime.GOOS)
t.Errorf("Expected %q, got %q", a, b)
}
}
isEq(t, hh.String(), "/r/users/helmtest")
isEq(t, hh.Registry(), "/r/users/helmtest/registry")
isEq(t, hh.Repository(), "/r/users/helmtest/repository")
isEq(t, hh.RepositoryFile(), "/r/users/helmtest/repository/repositories.yaml")
isEq(t, hh.Cache(), "/r/users/helmtest/repository/cache")
isEq(t, hh.CacheIndex("t"), "/r/users/helmtest/repository/cache/t-index.yaml")
isEq(t, hh.Starters(), "/r/users/helmtest/starters")
isEq(t, hh.Archive(), "/r/users/helmtest/cache/archive")
}
func TestHelmHome_expand(t *testing.T) {
if Home("$HOME").String() == "$HOME" {
t.Error("expected variable expansion")
}
}

@ -0,0 +1,81 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helmpath
import (
"fmt"
"path/filepath"
)
// This helper builds paths to Helm's configuration, cache and data paths.
var lp = lazypath{name: "helm"}
// ConfigPath returns the path where Helm stores configuration.
func ConfigPath() string {
return lp.configPath("")
}
// CachePath returns the path where Helm stores cached objects.
func CachePath() string {
return lp.cachePath("")
}
// DataPath returns the path where Helm stores data.
func DataPath() string {
return lp.dataPath("")
}
// Registry returns the path to the local registry cache.
func Registry() string {
return lp.cachePath("registry")
}
// RepositoryFile returns the path to the repositories.yaml file.
func RepositoryFile() string {
return lp.configPath("repositories.yaml")
}
// RepositoryCache returns the cache path for repository metadata.
func RepositoryCache() string {
return lp.cachePath("repository")
}
// CacheIndex returns the path to an index for the given named repository.
func CacheIndex(name string) string {
target := fmt.Sprintf("%s-index.yaml", name)
if name == "" {
target = "index.yaml"
}
return filepath.Join(RepositoryCache(), target)
}
// Starters returns the path to the Helm starter packs.
func Starters() string {
return lp.dataPath("starters")
}
// PluginCache returns the cache path for plugins.
func PluginCache() string {
return lp.cachePath("plugins")
}
// Plugins returns the path to the plugins directory.
func Plugins() string {
return lp.dataPath("plugins")
}
// Archive returns the path to download chart archives.
func Archive() string {
return lp.cachePath("archive")
}

@ -0,0 +1,52 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package helmpath
import (
"os"
"runtime"
"testing"
"helm.sh/helm/pkg/helmpath/xdg"
)
func TestHelmHome(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "/cache")
os.Setenv(xdg.ConfigHomeEnvVar, "/config")
os.Setenv(xdg.DataHomeEnvVar, "/data")
isEq := func(t *testing.T, got, expected string) {
t.Helper()
if expected != got {
t.Error(runtime.GOOS)
t.Errorf("Expected %q, got %q", expected, got)
}
}
isEq(t, CachePath(), "/cache/helm")
isEq(t, ConfigPath(), "/config/helm")
isEq(t, DataPath(), "/data/helm")
isEq(t, RepositoryFile(), "/config/helm/repositories.yaml")
isEq(t, RepositoryCache(), "/cache/helm/repository")
isEq(t, CacheIndex("t"), "/cache/helm/repository/t-index.yaml")
isEq(t, CacheIndex(""), "/cache/helm/repository/index.yaml")
isEq(t, Starters(), "/data/helm/starters")
isEq(t, Archive(), "/cache/helm/archive")
// test to see if lazy-loading environment variables at runtime works
os.Setenv(xdg.CacheHomeEnvVar, "/cache2")
isEq(t, CachePath(), "/cache2/helm")
}

@ -16,23 +16,34 @@
package helmpath package helmpath
import ( import (
"os"
"testing" "testing"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestHelmHome(t *testing.T) { func TestHelmHome(t *testing.T) {
hh := Home("r:\\users\\helmtest") os.Setenv(xdg.XDGCacheHomeEnvVar, "c:\\")
os.Setenv(xdg.XDGConfigHomeEnvVar, "d:\\")
os.Setenv(xdg.XDGDataHomeEnvVar, "e:\\")
isEq := func(t *testing.T, a, b string) { isEq := func(t *testing.T, a, b string) {
if a != b { if a != b {
t.Errorf("Expected %q, got %q", b, a) t.Errorf("Expected %q, got %q", b, a)
} }
} }
isEq(t, hh.String(), "r:\\users\\helmtest") isEq(t, CachePath(), "c:\\helm")
isEq(t, hh.Registry(), "r:\\users\\helmtest\\registry") isEq(t, ConfigPath(), "d:\\helm")
isEq(t, hh.Repository(), "r:\\users\\helmtest\\repository") isEq(t, DataPath(), "e:\\helm")
isEq(t, hh.RepositoryFile(), "r:\\users\\helmtest\\repository\\repositories.yaml") isEq(t, RepositoryFile(), "d:\\helm\\repositories.yaml")
isEq(t, hh.Cache(), "r:\\users\\helmtest\\repository\\cache") isEq(t, RepositoryCache(), "c:\\helm\\repository")
isEq(t, hh.CacheIndex("t"), "r:\\users\\helmtest\\repository\\cache\\t-index.yaml") isEq(t, CacheIndex("t"), "c:\\helm\\repository\\t-index.yaml")
isEq(t, hh.Starters(), "r:\\users\\helmtest\\starters") isEq(t, CacheIndex(""), "c:\\helm\\repository\\index.yaml")
isEq(t, hh.Archive(), "r:\\users\\helmtest\\cache\\archive") isEq(t, Starters(), "e:\\helm\\starters")
isEq(t, Archive(), "c:\\helm\\archive")
// test to see if lazy-loading environment variables at runtime works
os.Setenv(xdg.CacheHomeEnvVar, "f:\\")
isEq(t, CachePath(), "f:\\helm")
} }

@ -0,0 +1,52 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helmpath
import (
"os"
"path/filepath"
"helm.sh/helm/pkg/helmpath/xdg"
)
// lazypath is an lazy-loaded path buffer for the XDG base directory specification.
//
// name is the base name of the application referenced in the base directories.
type lazypath struct {
name string
}
func (l lazypath) path(envVar string, defaultFn func() string, file string) string {
base := os.Getenv(envVar)
if base == "" {
base = defaultFn()
}
return filepath.Join(base, l.name, file)
}
// cachePath defines the base directory relative to which user specific non-essential data files
// should be stored.
func (l lazypath) cachePath(file string) string {
return l.path(xdg.CacheHomeEnvVar, cacheHome, file)
}
// configPath defines the base directory relative to which user specific configuration files should
// be stored.
func (l lazypath) configPath(file string) string {
return l.path(xdg.ConfigHomeEnvVar, configHome, file)
}
// dataPath defines the base directory relative to which user specific data files should be stored.
func (l lazypath) dataPath(file string) string {
return l.path(xdg.DataHomeEnvVar, dataHome, file)
}

@ -0,0 +1,34 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
func dataHome() string {
return filepath.Join(homedir.HomeDir(), "Library")
}
func configHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Preferences")
}
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Caches")
}

@ -0,0 +1,86 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package helmpath
import (
"os"
"path/filepath"
"testing"
"helm.sh/helm/pkg/helmpath/xdg"
"k8s.io/client-go/util/homedir"
)
const (
appName string = "helm"
testFile string = "test.txt"
)
var lazy = lazypath{name: appName}
func TestDataPath(t *testing.T) {
os.Unsetenv(xdg.DataHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), "Library", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
os.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
}
func TestConfigPath(t *testing.T) {
os.Unsetenv(xdg.ConfigHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), "Library", "Preferences", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
os.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
}
func TestCachePath(t *testing.T) {
os.Unsetenv(xdg.CacheHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), "Library", "Caches", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
os.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
}

@ -0,0 +1,45 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows,!darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
// dataHome defines the base directory relative to which user specific data files should be stored.
//
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share is used.
func dataHome() string {
return filepath.Join(homedir.HomeDir(), ".local", "share")
}
// configHome defines the base directory relative to which user specific configuration files should
// be stored.
//
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config is used.
func configHome() string {
return filepath.Join(homedir.HomeDir(), ".config")
}
// cacheHome defines the base directory relative to which user specific non-essential data files
// should be stored.
//
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache is used.
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), ".cache")
}

@ -0,0 +1,87 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows,!darwin
package helmpath
import (
"os"
"path/filepath"
"testing"
"k8s.io/client-go/util/homedir"
"helm.sh/helm/pkg/helmpath/xdg"
)
const (
appName string = "helm"
testFile string = "test.txt"
)
var lazy = lazypath{name: appName}
func TestDataPath(t *testing.T) {
os.Unsetenv(xdg.DataHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
os.Setenv(xdg.DataHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
}
func TestConfigPath(t *testing.T) {
os.Unsetenv(xdg.ConfigHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
os.Setenv(xdg.ConfigHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
}
func TestCachePath(t *testing.T) {
os.Unsetenv(xdg.CacheHomeEnvVar)
expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
os.Setenv(xdg.CacheHomeEnvVar, "/tmp")
expected = filepath.Join("/tmp", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
}

@ -0,0 +1,30 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package helmpath
import "os"
func dataHome() string {
return configHome()
}
func configHome() string {
return os.Getenv("APPDATA")
}
func cacheHome() string {
return os.Getenv("TEMP")
}

@ -0,0 +1,88 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package helmpath
import (
"os"
"path/filepath"
"testing"
"k8s.io/client-go/util/homedir"
)
const (
appName string = "helm"
testFile string = "test.txt"
)
var lazy = lazypath{name: appName}
func TestDataPath(t *testing.T) {
os.Unsetenv(DataHomeEnvVar)
os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo"))
expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
os.Setenv(DataHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")))
expected = filepath.Join(homedir.HomeDir(), "xdg" appName, testFile)
if lazy.dataPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile))
}
}
func TestConfigPath(t *testing.T) {
os.Unsetenv(xdg.ConfigHomeEnvVar)
os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo"))
expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
os.Setenv(xdg.ConfigHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")))
expected = filepath.Join(homedir.HomeDir(), "xdg" appName, testFile)
if lazy.configPath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile))
}
}
func TestCachePath(t *testing.T) {
os.Unsetenv(CacheHomeEnvVar)
os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo"))
expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
os.Setenv(CacheHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")))
expected = filepath.Join(homedir.HomeDir(), "xdg" appName, testFile)
if lazy.cachePath(testFile) != expected {
t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile))
}
}

@ -0,0 +1,28 @@
// Copyright The Helm Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xdg
const (
// CacheHomeEnvVar is the environment variable used by the
// XDG base directory specification for the cache directory.
CacheHomeEnvVar = "XDG_CACHE_HOME"
// ConfigHomeEnvVar is the environment variable used by the
// XDG base directory specification for the config directory.
ConfigHomeEnvVar = "XDG_CONFIG_HOME"
// DataHomeEnvVar is the environment variable used by the
// XDG base directory specification for the data directory.
DataHomeEnvVar = "XDG_DATA_HOME"
)

@ -25,15 +25,13 @@ import (
type base struct { type base struct {
// Source is the reference to a plugin // Source is the reference to a plugin
Source string Source string
// HelmHome is the $HELM_HOME directory
HelmHome helmpath.Home
} }
func newBase(source string, home helmpath.Home) base { func newBase(source string) base {
return base{source, home} return base{source}
} }
// link creates a symlink from the plugin source to $HELM_HOME. // link creates a symlink from the plugin source to the base path.
func (b *base) link(from string) error { func (b *base) link(from string) error {
debug("symlinking %s to %s", from, b.Path()) debug("symlinking %s to %s", from, b.Path())
return os.Symlink(from, b.Path()) return os.Symlink(from, b.Path())
@ -44,5 +42,5 @@ func (b *base) Path() string {
if b.Source == "" { if b.Source == "" {
return "" return ""
} }
return filepath.Join(b.HelmHome.Plugins(), filepath.Base(b.Source)) return filepath.Join(helmpath.Plugins(), filepath.Base(b.Source))
} }

@ -67,7 +67,7 @@ func NewExtractor(source string) (Extractor, error) {
} }
// NewHTTPInstaller creates a new HttpInstaller. // NewHTTPInstaller creates a new HttpInstaller.
func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) { func NewHTTPInstaller(source string) (*HTTPInstaller, error) {
key, err := cache.Key(source) key, err := cache.Key(source)
if err != nil { if err != nil {
@ -90,9 +90,9 @@ func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error)
} }
i := &HTTPInstaller{ i := &HTTPInstaller{
CacheDir: home.Path("cache", "plugins", key), CacheDir: filepath.Join(helmpath.PluginCache(), key),
PluginName: stripPluginName(filepath.Base(source)), PluginName: stripPluginName(filepath.Base(source)),
base: newBase(source, home), base: newBase(source),
extractor: extractor, extractor: extractor,
getter: get, getter: get,
} }
@ -112,7 +112,8 @@ func stripPluginName(name string) string {
return re.ReplaceAllString(strippedName, `$1`) return re.ReplaceAllString(strippedName, `$1`)
} }
// Install downloads and extracts the tarball into the cache directory and creates a symlink to the plugin directory in $HELM_HOME. // Install downloads and extracts the tarball into the cache directory
// and creates a symlink to the plugin directory.
// //
// Implements Installer. // Implements Installer.
func (i *HTTPInstaller) Install() error { func (i *HTTPInstaller) Install() error {
@ -156,7 +157,7 @@ func (i HTTPInstaller) Path() string {
if i.base.Source == "" { if i.base.Source == "" {
return "" return ""
} }
return filepath.Join(i.base.HelmHome.Plugins(), i.PluginName) return filepath.Join(helmpath.Plugins(), i.PluginName)
} }
// Extract extracts compressed archives // Extract extracts compressed archives

@ -18,12 +18,13 @@ package installer // import "helm.sh/helm/pkg/plugin/installer"
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
@ -57,18 +58,14 @@ func TestStripName(t *testing.T) {
func TestHTTPInstaller(t *testing.T) { func TestHTTPInstaller(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh) if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil {
if err := os.MkdirAll(home.Plugins(), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err)
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
} }
i, err := NewForSource(source, "0.0.1", home) i, err := NewForSource(source, "0.0.1")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -93,8 +90,8 @@ func TestHTTPInstaller(t *testing.T) {
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Error(err)
} }
if i.Path() != home.Path("plugins", "fake-plugin") { if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") {
t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }
// Install again to test plugin exists error // Install again to test plugin exists error
@ -108,18 +105,14 @@ func TestHTTPInstaller(t *testing.T) {
func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh) if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil {
if err := os.MkdirAll(home.Plugins(), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err)
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
} }
i, err := NewForSource(source, "0.0.2", home) i, err := NewForSource(source, "0.0.2")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -144,18 +137,14 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
func TestHTTPInstallerUpdate(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh) if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil {
if err := os.MkdirAll(home.Plugins(), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err)
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
} }
i, err := NewForSource(source, "0.0.1", home) i, err := NewForSource(source, "0.0.1")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -180,8 +169,8 @@ func TestHTTPInstallerUpdate(t *testing.T) {
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Error(err)
} }
if i.Path() != home.Path("plugins", "fake-plugin") { if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") {
t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }
// Update plugin, should fail because it is not implemented // Update plugin, should fail because it is not implemented

@ -23,8 +23,6 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/helmpath"
) )
// ErrMissingMetadata indicates that plugin.yaml is missing. // ErrMissingMetadata indicates that plugin.yaml is missing.
@ -35,18 +33,18 @@ var Debug bool
// Installer provides an interface for installing helm client plugins. // Installer provides an interface for installing helm client plugins.
type Installer interface { type Installer interface {
// Install adds a plugin to $HELM_HOME. // Install adds a plugin.
Install() error Install() error
// Path is the directory of the installed plugin. // Path is the directory of the installed plugin.
Path() string Path() string
// Update updates a plugin to $HELM_HOME. // Update updates a plugin.
Update() error Update() error
} }
// Install installs a plugin to $HELM_HOME. // Install installs a plugin.
func Install(i Installer) error { func Install(i Installer) error {
if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) { if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) {
return errors.New(`plugin home "$HELM_HOME/plugins" does not exist`) return errors.New(`plugin home "$XDG_CONFIG_HOME/helm/plugins" does not exist`)
} }
if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) {
@ -56,7 +54,7 @@ func Install(i Installer) error {
return i.Install() return i.Install()
} }
// Update updates a plugin in $HELM_HOME. // Update updates a plugin.
func Update(i Installer) error { func Update(i Installer) error {
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
return errors.New("plugin does not exist") return errors.New("plugin does not exist")
@ -66,19 +64,19 @@ func Update(i Installer) error {
} }
// NewForSource determines the correct Installer for the given source. // NewForSource determines the correct Installer for the given source.
func NewForSource(source, version string, home helmpath.Home) (Installer, error) { func NewForSource(source, version string) (Installer, error) {
// Check if source is a local directory // Check if source is a local directory
if isLocalReference(source) { if isLocalReference(source) {
return NewLocalInstaller(source, home) return NewLocalInstaller(source)
} else if isRemoteHTTPArchive(source) { } else if isRemoteHTTPArchive(source) {
return NewHTTPInstaller(source, home) return NewHTTPInstaller(source)
} }
return NewVCSInstaller(source, version, home) return NewVCSInstaller(source, version)
} }
// FindSource determines the correct Installer for the given source. // FindSource determines the correct Installer for the given source.
func FindSource(location string, home helmpath.Home) (Installer, error) { func FindSource(location string) (Installer, error) {
installer, err := existingVCSRepo(location, home) installer, err := existingVCSRepo(location)
if err != nil && err.Error() == "Cannot detect VCS" { if err != nil && err.Error() == "Cannot detect VCS" {
return installer, errors.New("cannot get information about plugin source") return installer, errors.New("cannot get information about plugin source")
} }

@ -19,8 +19,6 @@ import (
"path/filepath" "path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/helmpath"
) )
// LocalInstaller installs plugins from the filesystem. // LocalInstaller installs plugins from the filesystem.
@ -29,18 +27,18 @@ type LocalInstaller struct {
} }
// NewLocalInstaller creates a new LocalInstaller. // NewLocalInstaller creates a new LocalInstaller.
func NewLocalInstaller(source string, home helmpath.Home) (*LocalInstaller, error) { func NewLocalInstaller(source string) (*LocalInstaller, error) {
src, err := filepath.Abs(source) src, err := filepath.Abs(source)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to get absolute path to plugin") return nil, errors.Wrap(err, "unable to get absolute path to plugin")
} }
i := &LocalInstaller{ i := &LocalInstaller{
base: newBase(src, home), base: newBase(src),
} }
return i, nil return i, nil
} }
// Install creates a symlink to the plugin directory in $HELM_HOME. // Install creates a symlink to the plugin directory.
// //
// Implements Installer. // Implements Installer.
func (i *LocalInstaller) Install() error { func (i *LocalInstaller) Install() error {

@ -21,22 +21,15 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
var _ Installer = new(LocalInstaller) var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) { func TestLocalInstaller(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh)
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
}
// Make a temp dir // Make a temp dir
tdir, err := ioutil.TempDir("", "helm-installer-") tdir, err := ioutil.TempDir("", "helm-installer-")
@ -49,7 +42,7 @@ func TestLocalInstaller(t *testing.T) {
} }
source := "../testdata/plugdir/echo" source := "../testdata/plugdir/echo"
i, err := NewForSource(source, "", home) i, err := NewForSource(source, "")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -58,7 +51,7 @@ func TestLocalInstaller(t *testing.T) {
t.Error(err) t.Error(err)
} }
if i.Path() != home.Path("plugins", "echo") { if i.Path() != filepath.Join(helmpath.Plugins(), "echo") {
t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
} }

@ -17,6 +17,7 @@ package installer // import "helm.sh/helm/pkg/plugin/installer"
import ( import (
"os" "os"
"path/filepath"
"sort" "sort"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
@ -34,25 +35,25 @@ type VCSInstaller struct {
base base
} }
func existingVCSRepo(location string, home helmpath.Home) (Installer, error) { func existingVCSRepo(location string) (Installer, error) {
repo, err := vcs.NewRepo("", location) repo, err := vcs.NewRepo("", location)
if err != nil { if err != nil {
return nil, err return nil, err
} }
i := &VCSInstaller{ i := &VCSInstaller{
Repo: repo, Repo: repo,
base: newBase(repo.Remote(), home), base: newBase(repo.Remote()),
} }
return i, err return i, err
} }
// NewVCSInstaller creates a new VCSInstaller. // NewVCSInstaller creates a new VCSInstaller.
func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) { func NewVCSInstaller(source, version string) (*VCSInstaller, error) {
key, err := cache.Key(source) key, err := cache.Key(source)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cachedpath := home.Path("cache", "plugins", key) cachedpath := filepath.Join(helmpath.PluginCache(), key)
repo, err := vcs.NewRepo(source, cachedpath) repo, err := vcs.NewRepo(source, cachedpath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -60,12 +61,12 @@ func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller,
i := &VCSInstaller{ i := &VCSInstaller{
Repo: repo, Repo: repo,
Version: version, Version: version,
base: newBase(source, home), base: newBase(source),
} }
return i, err return i, err
} }
// Install clones a remote repository and creates a symlink to the plugin directory in HELM_HOME. // Install clones a remote repository and creates a symlink to the plugin directory.
// //
// Implements Installer. // Implements Installer.
func (i *VCSInstaller) Install() error { func (i *VCSInstaller) Install() error {

@ -17,13 +17,13 @@ package installer // import "helm.sh/helm/pkg/plugin/installer"
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/Masterminds/vcs" "github.com/Masterminds/vcs"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
@ -49,15 +49,11 @@ func (r *testRepo) UpdateVersion(version string) error {
} }
func TestVCSInstaller(t *testing.T) { func TestVCSInstaller(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh) if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil {
if err := os.MkdirAll(home.Plugins(), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err)
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
} }
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
@ -67,7 +63,7 @@ func TestVCSInstaller(t *testing.T) {
tags: []string{"0.1.0", "0.1.1"}, tags: []string{"0.1.0", "0.1.1"},
} }
i, err := NewForSource(source, "~0.1.0", home) i, err := NewForSource(source, "~0.1.0")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
@ -87,8 +83,8 @@ func TestVCSInstaller(t *testing.T) {
if repo.current != "0.1.1" { if repo.current != "0.1.1" {
t.Errorf("expected version '0.1.1', got %q", repo.current) t.Errorf("expected version '0.1.1', got %q", repo.current)
} }
if i.Path() != home.Path("plugins", "helm-env") { if i.Path() != filepath.Join(helmpath.Plugins(), "helm-env") {
t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
// Install again to test plugin exists error // Install again to test plugin exists error
@ -99,7 +95,7 @@ func TestVCSInstaller(t *testing.T) {
} }
//Testing FindSource method, expect error because plugin code is not a cloned repository //Testing FindSource method, expect error because plugin code is not a cloned repository
if _, err := FindSource(i.Path(), home); err == nil { if _, err := FindSource(i.Path()); err == nil {
t.Error("expected error for inability to find plugin source, got none") t.Error("expected error for inability to find plugin source, got none")
} else if err.Error() != "cannot get information about plugin source" { } else if err.Error() != "cannot get information about plugin source" {
t.Errorf("expected error for inability to find plugin source, got (%v)", err) t.Errorf("expected error for inability to find plugin source, got (%v)", err)
@ -107,21 +103,13 @@ func TestVCSInstaller(t *testing.T) {
} }
func TestVCSInstallerNonExistentVersion(t *testing.T) { func TestVCSInstallerNonExistentVersion(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-") ensure.HelmHome(t)
if err != nil { defer ensure.CleanHomeDirs(t)
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh)
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
}
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
version := "0.2.0" version := "0.2.0"
i, err := NewForSource(source, version, home) i, err := NewForSource(source, version)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
@ -139,21 +127,12 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
} }
} }
func TestVCSInstallerUpdate(t *testing.T) { func TestVCSInstallerUpdate(t *testing.T) {
ensure.HelmHome(t)
hh, err := ioutil.TempDir("", "helm-home-") defer ensure.CleanHomeDirs(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh)
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
}
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
i, err := NewForSource(source, "", home) i, err := NewForSource(source, "")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
@ -176,7 +155,7 @@ func TestVCSInstallerUpdate(t *testing.T) {
} }
// Test FindSource method for positive result // Test FindSource method for positive result
pluginInfo, err := FindSource(i.Path(), home) pluginInfo, err := FindSource(i.Path())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -26,6 +26,7 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
helm_env "helm.sh/helm/pkg/cli" helm_env "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath"
) )
const pluginFileName = "plugin.yaml" const pluginFileName = "plugin.yaml"
@ -220,17 +221,15 @@ func SetupPluginEnv(settings helm_env.EnvSettings,
"HELM_PLUGIN_NAME": shortName, "HELM_PLUGIN_NAME": shortName,
"HELM_PLUGIN_DIR": base, "HELM_PLUGIN_DIR": base,
"HELM_BIN": os.Args[0], "HELM_BIN": os.Args[0],
"HELM_PLUGIN": helmpath.Plugins(),
// Set vars that may not have been set, and save client the
// trouble of re-parsing.
"HELM_PLUGIN": settings.PluginDirs(),
"HELM_HOME": settings.Home.String(),
// Set vars that convey common information. // Set vars that convey common information.
"HELM_PATH_REPOSITORY": settings.Home.Repository(), "HELM_PATH_REPOSITORY_FILE": helmpath.RepositoryFile(),
"HELM_PATH_REPOSITORY_FILE": settings.Home.RepositoryFile(), "HELM_PATH_REPOSITORY_CACHE": helmpath.RepositoryCache(),
"HELM_PATH_CACHE": settings.Home.Cache(), "HELM_PATH_STARTER": helmpath.Starters(),
"HELM_PATH_STARTER": settings.Home.Starters(), "HELM_PATH_CACHE": helmpath.CachePath(),
"HELM_PATH_CONFIG": helmpath.ConfigPath(),
"HELM_PATH_DATA": helmpath.DataPath(),
} { } {
os.Setenv(key, val) os.Setenv(key, val)
} }

@ -7,7 +7,7 @@ echo $*
echo "ENVIRONMENT" echo "ENVIRONMENT"
echo $TILLER_HOST echo $TILLER_HOST
echo $HELM_HOME echo $HELM_PATH_CONFIG
$HELM_BIN --host $TILLER_HOST ls --all $HELM_BIN --host $TILLER_HOST ls --all

@ -17,6 +17,8 @@ limitations under the License.
package repo // import "helm.sh/helm/pkg/repo" package repo // import "helm.sh/helm/pkg/repo"
import ( import (
"crypto/rand"
"encoding/base64"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
@ -29,13 +31,13 @@ import (
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/provenance"
) )
// Entry represents a collection of parameters for chart repository // Entry represents a collection of parameters for chart repository
type Entry struct { type Entry struct {
Name string `json:"name"` Name string `json:"name"`
Cache string `json:"cache"`
URL string `json:"url"` URL string `json:"url"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
@ -112,10 +114,7 @@ func (r *ChartRepository) Load() error {
} }
// DownloadIndexFile fetches the index from a repository. // DownloadIndexFile fetches the index from a repository.
// func (r *ChartRepository) DownloadIndexFile() error {
// cachePath is prepended to any index that does not have an absolute path. This
// is for pre-2.2.0 repo files.
func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
var indexURL string var indexURL string
parsedURL, err := url.Parse(r.Config.URL) parsedURL, err := url.Parse(r.Config.URL)
if err != nil { if err != nil {
@ -139,18 +138,7 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
return err return err
} }
// In Helm 2.2.0 the config.cache was accidentally switched to an absolute return ioutil.WriteFile(helmpath.CacheIndex(r.Config.Name), index, 0644)
// path, which broke backward compatibility. This fixes it by prepending a
// global cache path to relative paths.
//
// It is changed on DownloadIndexFile because that was the method that
// originally carried the cache path.
cp := r.Config.Cache
if !filepath.IsAbs(cp) {
cp = filepath.Join(cachePath, cp)
}
return ioutil.WriteFile(cp, index, 0644)
} }
// Index generates an index for the chart repository and writes an index.yaml file. // Index generates an index for the chart repository and writes an index.yaml file.
@ -203,11 +191,9 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF
func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { 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 // Download and write the index file to a temporary location
tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file") buf := make([]byte, 20)
if err != nil { rand.Read(buf)
return "", errors.Errorf("cannot write index file for repository requested") name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-")
}
defer os.Remove(tempIndexFile.Name())
c := Entry{ c := Entry{
URL: repoURL, URL: repoURL,
@ -216,17 +202,18 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
CertFile: certFile, CertFile: certFile,
KeyFile: keyFile, KeyFile: keyFile,
CAFile: caFile, CAFile: caFile,
Name: name,
} }
r, err := NewChartRepository(&c, getters) r, err := NewChartRepository(&c, getters)
if err != nil { if err != nil {
return "", err return "", err
} }
if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil { if err := r.DownloadIndexFile(); err != nil {
return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL) return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
} }
// Read the index file for the repository to get chart information and return chart URL // Read the index file for the repository to get chart information and return chart URL
repoIndex, err := LoadIndexFile(tempIndexFile.Name()) repoIndex, err := LoadIndexFile(helmpath.CacheIndex(name))
if err != nil { if err != nil {
return "", err return "", err
} }

@ -28,11 +28,14 @@ import (
"testing" "testing"
"time" "time"
"sigs.k8s.io/yaml"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"sigs.k8s.io/yaml" "helm.sh/helm/pkg/helmpath/xdg"
) )
const ( const (
@ -129,6 +132,9 @@ func (g *CustomGetter) Get(href string) (*bytes.Buffer, error) {
} }
func TestIndexCustomSchemeDownload(t *testing.T) { func TestIndexCustomSchemeDownload(t *testing.T) {
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
repoName := "gcs-repo" repoName := "gcs-repo"
repoURL := "gs://some-gcs-bucket" repoURL := "gs://some-gcs-bucket"
myCustomGetter := &CustomGetter{} myCustomGetter := &CustomGetter{}
@ -155,7 +161,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) {
} }
defer os.Remove(tempIndexFile.Name()) defer os.Remove(tempIndexFile.Name())
if err := repo.DownloadIndexFile(tempIndexFile.Name()); err != nil { if err := repo.DownloadIndexFile(); err != nil {
t.Fatalf("Failed to download index file: %v", err) t.Fatalf("Failed to download index file: %v", err)
} }
@ -276,6 +282,8 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) {
} }
func TestFindChartInRepoURL(t *testing.T) { func TestFindChartInRepoURL(t *testing.T) {
setupCacheHome(t)
srv, err := startLocalServerForTests(nil) srv, err := startLocalServerForTests(nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -300,6 +308,8 @@ func TestFindChartInRepoURL(t *testing.T) {
} }
func TestErrorFindChartInRepoURL(t *testing.T) { func TestErrorFindChartInRepoURL(t *testing.T) {
setupCacheHome(t)
_, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", getter.All(cli.EnvSettings{})) _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", getter.All(cli.EnvSettings{}))
if err == nil { if err == nil {
t.Errorf("Expected error for bad chart URL, but did not get any errors") t.Errorf("Expected error for bad chart URL, but did not get any errors")
@ -364,3 +374,16 @@ func TestResolveReferenceURL(t *testing.T) {
t.Errorf("%s", chartURL) t.Errorf("%s", chartURL)
} }
} }
func setupCacheHome(t *testing.T) {
t.Helper()
d, err := ioutil.TempDir("", "helm")
if err != nil {
t.Fatal(err)
}
os.Setenv(xdg.CacheHomeEnvVar, d)
if err := os.MkdirAll(helmpath.RepositoryCache(), 0755); err != nil {
t.Fatal(err)
}
}

@ -19,12 +19,12 @@ package repo
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
) )
const ( const (
@ -135,31 +135,25 @@ func TestDownloadIndexFile(t *testing.T) {
} }
defer srv.Close() defer srv.Close()
dirName, err := ioutil.TempDir("", "tmp") setupCacheHome(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dirName)
indexFilePath := filepath.Join(dirName, testRepo+"-index.yaml")
r, err := NewChartRepository(&Entry{ r, err := NewChartRepository(&Entry{
Name: testRepo, Name: testRepo,
URL: srv.URL, URL: srv.URL,
Cache: indexFilePath,
}, getter.All(cli.EnvSettings{})) }, getter.All(cli.EnvSettings{}))
if err != nil { if err != nil {
t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) t.Errorf("Problem creating chart repository from %s: %v", testRepo, err)
} }
if err := r.DownloadIndexFile(""); err != nil { if err := r.DownloadIndexFile(); err != nil {
t.Errorf("%#v", err) t.Errorf("%#v", err)
} }
if _, err := os.Stat(indexFilePath); err != nil { if _, err := os.Stat(helmpath.CacheIndex(testRepo)); err != nil {
t.Errorf("error finding created index file: %#v", err) t.Errorf("error finding created index file: %#v", err)
} }
b, err := ioutil.ReadFile(indexFilePath) b, err := ioutil.ReadFile(helmpath.CacheIndex(testRepo))
if err != nil { if err != nil {
t.Errorf("error reading index file: %#v", err) t.Errorf("error reading index file: %#v", err)
} }

@ -17,7 +17,6 @@ limitations under the License.
package repo // import "helm.sh/helm/pkg/repo" package repo // import "helm.sh/helm/pkg/repo"
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"time" "time"
@ -30,7 +29,7 @@ import (
// is fixable. // is fixable.
var ErrRepoOutOfDate = errors.New("repository file is out of date") var ErrRepoOutOfDate = errors.New("repository file is out of date")
// File represents the repositories.yaml file in $HELM_HOME // File represents the repositories.yaml file
type File struct { type File struct {
APIVersion string `json:"apiVersion"` APIVersion string `json:"apiVersion"`
Generated time.Time `json:"generated"` Generated time.Time `json:"generated"`
@ -78,7 +77,6 @@ func LoadFile(path string) (*File, error) {
r.Add(&Entry{ r.Add(&Entry{
Name: k, Name: k,
URL: v, URL: v,
Cache: fmt.Sprintf("%s-index.yaml", k),
}) })
} }
return r, ErrRepoOutOfDate return r, ErrRepoOutOfDate

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save