diff --git a/Makefile b/Makefile index 796806ab6..2c195978b 100644 --- a/Makefile +++ b/Makefile @@ -68,13 +68,13 @@ test: test-unit test-unit: vendor @echo @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 test-coverage: vendor @echo @echo "==> Running unit tests with coverage <==" - @ HELM_HOME=/no_such_dir ./scripts/coverage.sh + @ ./scripts/coverage.sh .PHONY: test-style test-style: vendor $(GOLANGCI_LINT) diff --git a/cmd/helm/create.go b/cmd/helm/create.go index a5fed1e04..4a7dd676c 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -26,6 +26,7 @@ import ( "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chartutil" + "helm.sh/helm/pkg/helmpath" ) const createDesc = ` @@ -86,7 +87,7 @@ func (o *createOptions) run(out io.Writer) error { if o.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 filepath.IsAbs(o.starter) { lstarter = o.starter diff --git a/cmd/helm/create_test.go b/cmd/helm/create_test.go index 7979b7c2e..76a2c5938 100644 --- a/cmd/helm/create_test.go +++ b/cmd/helm/create_test.go @@ -23,16 +23,18 @@ import ( "path/filepath" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chartutil" + "helm.sh/helm/pkg/helmpath" ) func TestCreateCmd(t *testing.T) { - tdir := testTempDir(t) - defer testChdir(t, tdir)() - cname := "testchart" + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) + defer testChdir(t, helmpath.CachePath())() // Run a create if _, _, err := executeActionCommand("create " + cname); err != nil { @@ -61,17 +63,14 @@ func TestCreateCmd(t *testing.T) { } func TestCreateStarterCmd(t *testing.T) { - defer resetEnv()() - cname := "testchart" - // Make a temp dir - tdir := testTempDir(t) - - hh := testHelmHome(t) - settings.Home = hh + defer resetEnv()() + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) + defer testChdir(t, helmpath.CachePath())() // Create a starter. - starterchart := hh.Starters() + starterchart := helmpath.Starters() os.Mkdir(starterchart, 0755) if dest, err := chartutil.Create("starterchart", starterchart); err != nil { 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) } - defer testChdir(t, tdir)() - // 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) return } @@ -131,16 +128,12 @@ func TestCreateStarterCmd(t *testing.T) { func TestCreateStarterAbsoluteCmd(t *testing.T) { defer resetEnv()() - + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) cname := "testchart" - // Make a temp dir - tdir := testTempDir(t) - - hh := testHelmHome(t) - settings.Home = hh // Create a starter. - starterchart := hh.Starters() + starterchart := helmpath.Starters() os.Mkdir(starterchart, 0755) if dest, err := chartutil.Create("starterchart", starterchart); err != nil { 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) } - defer testChdir(t, tdir)() + defer testChdir(t, helmpath.CachePath())() starterChartPath := filepath.Join(starterchart, "starterchart") // 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) return } diff --git a/cmd/helm/dependency_build.go b/cmd/helm/dependency_build.go index 1162fa883..0deb0f993 100644 --- a/cmd/helm/dependency_build.go +++ b/cmd/helm/dependency_build.go @@ -56,7 +56,6 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { man := &downloader.Manager{ Out: out, ChartPath: chartpath, - HelmHome: settings.Home, Keyring: client.Keyring, Getters: getter.All(settings), } diff --git a/cmd/helm/dependency_build_test.go b/cmd/helm/dependency_build_test.go index 0d4c5bf04..d6ef0c340 100644 --- a/cmd/helm/dependency_build_test.go +++ b/cmd/helm/dependency_build_test.go @@ -18,9 +18,12 @@ package main import ( "fmt" "os" + "path/filepath" "strings" "testing" + "helm.sh/helm/internal/test/ensure" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo/repotest" @@ -29,21 +32,21 @@ import ( func TestDependencyBuildCmd(t *testing.T) { defer resetEnv()() - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - srv := repotest.NewServer(hh.String()) + srv := repotest.NewServer(helmpath.ConfigPath()) defer srv.Stop() if _, err := srv.CopyCharts("testdata/testcharts/*.tgz"); err != nil { t.Fatal(err) } 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) } - 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) // 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. - 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 { t.Fatal(err) } // In the second pass, we want to remove the chart's request dependency, // 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 { t.Fatal(err) } @@ -79,7 +82,7 @@ func TestDependencyBuildCmd(t *testing.T) { } // 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 { t.Fatal(err) } @@ -90,7 +93,7 @@ func TestDependencyBuildCmd(t *testing.T) { t.Fatal(err) } - i, err := repo.LoadIndexFile(hh.CacheIndex("test")) + i, err := repo.LoadIndexFile(helmpath.CacheIndex("test")) if err != nil { t.Fatal(err) } diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index 623bce755..b9b211058 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -60,7 +60,6 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command { man := &downloader.Manager{ Out: out, ChartPath: chartpath, - HelmHome: settings.Home, Keyring: client.Keyring, SkipUpdate: client.SkipRefresh, Getters: getter.All(settings), diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go index bbe108973..0f1f5e5c7 100644 --- a/cmd/helm/dependency_update_test.go +++ b/cmd/helm/dependency_update_test.go @@ -23,8 +23,10 @@ import ( "strings" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chartutil" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo/repotest" @@ -33,10 +35,10 @@ import ( func TestDependencyUpdateCmd(t *testing.T) { defer resetEnv()() - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - srv := repotest.NewServer(hh.String()) + srv := repotest.NewServer(helmpath.ConfigPath()) defer srv.Stop() copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") if err != nil { @@ -48,11 +50,11 @@ func TestDependencyUpdateCmd(t *testing.T) { chartname := "depup" ch := createTestingMetadata(chartname, srv.URL()) md := ch.Metadata - if err := chartutil.SaveDir(ch, hh.String()); err != nil { + if err := chartutil.SaveDir(ch, helmpath.DataPath()); err != nil { 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 { t.Logf("Output: %s", out) t.Fatal(err) @@ -64,7 +66,7 @@ func TestDependencyUpdateCmd(t *testing.T) { } // 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 { t.Fatal(err) } @@ -74,7 +76,7 @@ func TestDependencyUpdateCmd(t *testing.T) { t.Fatal(err) } - i, err := repo.LoadIndexFile(hh.CacheIndex("test")) + i, err := repo.LoadIndexFile(helmpath.CacheIndex("test")) if err != nil { t.Fatal(err) } @@ -90,12 +92,12 @@ func TestDependencyUpdateCmd(t *testing.T) { {Name: "reqtest", Version: "0.1.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 { 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 { t.Logf("Output: %s", out) 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 // 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 { 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 { t.Fatalf("Unexpected %q", dontExpect) } @@ -116,10 +118,10 @@ func TestDependencyUpdateCmd(t *testing.T) { func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) { defer resetEnv()() - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - srv := repotest.NewServer(hh.String()) + srv := repotest.NewServer(helmpath.ConfigPath()) defer srv.Stop() copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") if err != nil { @@ -129,11 +131,11 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) { t.Logf("Listening on directory %s", srv.Root()) 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) } - _, 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 { 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) { defer resetEnv()() - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - srv := repotest.NewServer(hh.String()) + srv := repotest.NewServer(helmpath.ConfigPath()) defer srv.Stop() copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") if err != nil { @@ -160,11 +162,11 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { t.Logf("Listening on directory %s", srv.Root()) 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) } - _, 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 { t.Logf("Output: %s", output) t.Fatal(err) @@ -173,14 +175,14 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { // Chart repo is down 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 { t.Logf("Output: %s", output) t.Fatal("Expected error, got nil") } // 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 { t.Fatal(err) } @@ -196,7 +198,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { } // 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") } } diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index bf5df52a1..152e69b54 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -30,10 +30,8 @@ import ( "helm.sh/helm/internal/test" "helm.sh/helm/pkg/action" "helm.sh/helm/pkg/chartutil" - "helm.sh/helm/pkg/helmpath" kubefake "helm.sh/helm/pkg/kube/fake" "helm.sh/helm/pkg/release" - "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/storage" "helm.sh/helm/pkg/storage/driver" ) @@ -45,20 +43,10 @@ func init() { } func TestMain(m *testing.M) { - os.Unsetenv("HELM_HOME") exitCode := m.Run() 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) { t.Helper() for _, tt := range tests { @@ -144,48 +132,6 @@ func executeActionCommand(cmd string) (*cobra.Command, string, error) { 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() { origSettings, origEnv := settings, os.Environ() return func() { diff --git a/cmd/helm/home.go b/cmd/helm/home.go deleted file mode 100644 index c15cb163d..000000000 --- a/cmd/helm/home.go +++ /dev/null @@ -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 -} diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 0faee1d11..90ffbb1b3 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -35,14 +35,28 @@ import ( ) 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 { skipRefresh bool // --skip-refresh pluginsFilename string // --plugins - - home helmpath.Home } type pluginsFileEntry struct { @@ -63,7 +77,6 @@ func newInitCmd(out io.Writer) *cobra.Command { Long: initDesc, Args: require.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - o.home = settings.Home return o.run(out) }, } @@ -77,13 +90,13 @@ func newInitCmd(out io.Writer) *cobra.Command { // run initializes local config. func (o *initOptions) run(out io.Writer) error { - if err := ensureDirectories(o.home, out); err != nil { + if err := ensureDirectories(out); err != nil { return err } - if err := ensureReposFile(o.home, out, o.skipRefresh); err != nil { + if err := ensureReposFile(out, o.skipRefresh); err != nil { return err } - if err := ensureRepoFileFormat(o.home.RepositoryFile(), out); err != nil { + if err := ensureRepoFileFormat(helmpath.RepositoryFile(), out); err != nil { return err } if o.pluginsFilename != "" { @@ -91,24 +104,29 @@ func (o *initOptions) run(out io.Writer) error { 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!") 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. -func ensureDirectories(home helmpath.Home, out io.Writer) error { - configDirectories := []string{ - home.String(), - home.Repository(), - home.Cache(), - home.Plugins(), - home.Starters(), - home.Archive(), +// If they do not exist, this function will create it. +func ensureDirectories(out io.Writer) error { + directories := []string{ + helmpath.CachePath(), + helmpath.ConfigPath(), + helmpath.DataPath(), + helmpath.RepositoryCache(), + helmpath.Plugins(), + helmpath.PluginCache(), + helmpath.Starters(), + helmpath.Archive(), } - for _, p := range configDirectories { + for _, p := range directories { if fi, err := os.Stat(p); err != nil { fmt.Fprintf(out, "Creating %s \n", p) if err := os.MkdirAll(p, 0755); err != nil { @@ -122,8 +140,8 @@ func ensureDirectories(home helmpath.Home, out io.Writer) error { return nil } -func ensureReposFile(home helmpath.Home, out io.Writer, skipRefresh bool) error { - repoFile := home.RepositoryFile() +func ensureReposFile(out io.Writer, skipRefresh bool) error { + repoFile := helmpath.RepositoryFile() if fi, err := os.Stat(repoFile); err != nil { fmt.Fprintf(out, "Creating %s \n", repoFile) 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 { - i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version, settings.Home) + i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version) if err != nil { return err } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index c0db7c184..125e017b9 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -21,33 +21,34 @@ import ( "os" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/helmpath" ) const testPluginsFile = "testdata/plugins.yaml" func TestEnsureHome(t *testing.T) { - hh := helmpath.Home(testTempDir(t)) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) b := bytes.NewBuffer(nil) - settings.Home = hh - if err := ensureDirectories(hh, b); err != nil { + if err := ensureDirectories(b); err != nil { t.Error(err) } - if err := ensureReposFile(hh, b, false); err != nil { + if err := ensureReposFile(b, false); err != nil { t.Error(err) } - if err := ensureReposFile(hh, b, true); err != nil { + if err := ensureReposFile(b, true); err != nil { t.Error(err) } - if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { + if err := ensureRepoFileFormat(helmpath.RepositoryFile(), b); err != nil { t.Error(err) } if err := ensurePluginsInstalled(testPluginsFile, b); err != nil { t.Error(err) } - expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache()} + expectedDirs := []string{helmpath.CachePath(), helmpath.ConfigPath(), helmpath.DataPath()} for _, dir := range expectedDirs { if fi, err := os.Stat(dir); err != nil { 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) } else if fi.IsDir() { 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) } else if len(plugins) != 1 { t.Errorf("Expected 1 plugin, got %d", len(plugins)) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index bfbe329cf..d47360967 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -28,6 +28,7 @@ import ( "helm.sh/helm/pkg/action" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart/loader" + "helm.sh/helm/pkg/cli/values" "helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/release" @@ -97,6 +98,7 @@ charts in a repository, use 'helm search'. func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewInstall(cfg) + valueOpts := &values.Options{} cmd := &cobra.Command{ Use: "install [NAME] [CHART]", @@ -104,7 +106,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: installDesc, Args: require.MinimumNArgs(1), RunE: func(_ *cobra.Command, args []string) error { - rel, err := runInstall(args, client, out) + rel, err := runInstall(args, client, valueOpts, out) if err != nil { return err } @@ -113,12 +115,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - addInstallFlags(cmd.Flags(), client) + addInstallFlags(cmd.Flags(), client, valueOpts) return cmd } -func addInstallFlags(f *pflag.FlagSet, client *action.Install) { +func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") @@ -129,11 +131,11 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install) { f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used") - addValueOptionsFlags(f, &client.ValueOptions) + addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) } -func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) { +func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)") f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") @@ -151,7 +153,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") } -func runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) { +func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { debug("Original chart version: %q", client.Version) if client.Version == "" && client.Devel { debug("setting version to >0.0.0-0") @@ -171,7 +173,8 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release. debug("CHART PATH: %s\n", cp) - if err := client.ValueOptions.MergeValues(settings); err != nil { + vals, err := valueOpts.MergeValues(settings) + if err != nil { return nil, err } @@ -195,7 +198,6 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release. man := &downloader.Manager{ Out: out, ChartPath: cp, - HelmHome: settings.Home, Keyring: client.ChartPathOptions.Keyring, SkipUpdate: false, Getters: getter.All(settings), @@ -210,7 +212,7 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release. } client.Namespace = getNamespace() - return client.Run(chartRequested) + return client.Run(chartRequested, vals) } // isChartInstallable validates if a chart can be installed diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 65797e67b..bab1ebb7c 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -25,6 +25,7 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/pkg/action" + "helm.sh/helm/pkg/cli/values" ) var longLintHelp = ` @@ -38,6 +39,7 @@ or recommendation, it will emit [WARNING] messages. func newLintCmd(out io.Writer) *cobra.Command { client := action.NewLint() + valueOpts := &values.Options{} cmd := &cobra.Command{ Use: "lint PATH", @@ -49,10 +51,11 @@ func newLintCmd(out io.Writer) *cobra.Command { paths = args } client.Namespace = getNamespace() - if err := client.ValueOptions.MergeValues(settings); err != nil { + vals, err := valueOpts.MergeValues(settings) + if err != nil { return err } - result := client.Run(paths) + result := client.Run(paths, vals) var message strings.Builder fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors)) for _, err := range result.Errors { @@ -72,7 +75,7 @@ func newLintCmd(out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") - addValueOptionsFlags(f, &client.ValueOptions) + addValueOptionsFlags(f, valueOpts) return cmd } diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index 8c8dc3495..e0d4012c8 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -26,6 +26,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/plugin" ) @@ -41,8 +42,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { return } - // debug("HELM_PLUGIN_DIRS=%s", settings.PluginDirs()) - found, err := findPlugins(settings.PluginDirs()) + found, err := findPlugins(helmpath.Plugins()) if err != nil { fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) return @@ -113,7 +113,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { func manuallyProcessArgs(args []string) ([]string, []string) { known := []string{} unknown := []string{} - kvargs := []string{"--context", "--home", "--namespace"} + kvargs := []string{"--context", "--namespace"} knownArg := func(a string) bool { for _, pre := range kvargs { if strings.HasPrefix(a, pre+"=") { @@ -126,7 +126,7 @@ func manuallyProcessArgs(args []string) ([]string, []string) { switch a := args[i]; a { case "--debug": known = append(known, a) - case "--context", "--home", "--namespace": + case "--context", "--namespace": known = append(known, a, args[i+1]) i++ default: diff --git a/cmd/helm/package.go b/cmd/helm/package.go index c33164a0a..a9dc254cf 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/pkg/action" + "helm.sh/helm/pkg/cli/values" "helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/getter" ) @@ -43,6 +44,7 @@ Versioned chart archives are used by Helm package repositories. func newPackageCmd(out io.Writer) *cobra.Command { client := action.NewPackage() + valueOpts := &values.Options{} cmd := &cobra.Command{ Use: "package [CHART_PATH] [...]", @@ -60,7 +62,8 @@ func newPackageCmd(out io.Writer) *cobra.Command { return errors.New("--keyring is required for signing a package") } } - if err := client.ValueOptions.MergeValues(settings); err != nil { + vals, err := valueOpts.MergeValues(settings) + if err != nil { return err } @@ -74,7 +77,6 @@ func newPackageCmd(out io.Writer) *cobra.Command { downloadManager := &downloader.Manager{ Out: ioutil.Discard, ChartPath: path, - HelmHome: settings.Home, Keyring: client.Keyring, Getters: getter.All(settings), Debug: settings.Debug, @@ -84,7 +86,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { return err } } - p, err := client.Run(path) + p, err := client.Run(path, vals) if err != nil { return err } @@ -102,7 +104,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) - addValueOptionsFlags(f, &client.ValueOptions) + addValueOptionsFlags(f, valueOpts) return cmd } diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index ac4f3aef6..32192d9bb 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chartutil" @@ -137,19 +138,16 @@ func TestPackage(t *testing.T) { if err != nil { t.Fatal(err) } - tmp := testTempDir(t) - t.Logf("Running tests in %s", tmp) - defer testChdir(t, tmp)() + ensure.HelmHome(t) + 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 { t.Fatal(err) } - ensureTestHome(t, helmpath.Home(tmp)) - - settings.Home = helmpath.Home(tmp) - for _, tt := range tests { buf := bytes.NewBuffer(nil) c := newPackageCmd(buf) @@ -203,14 +201,13 @@ func TestSetAppVersion(t *testing.T) { var ch *chart.Chart expectedAppVersion := "app-version-foo" - tmp := testTempDir(t) - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) c := newPackageCmd(&bytes.Buffer{}) flags := map[string]string{ - "destination": tmp, + "destination": helmpath.CachePath(), "app-version": expectedAppVersion, } setFlags(c, flags) @@ -218,7 +215,7 @@ func TestSetAppVersion(t *testing.T) { 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 { t.Errorf("expected file %q, got err %q", chartPath, err) } else if fi.Size() == 0 { @@ -270,8 +267,8 @@ func TestPackageValues(t *testing.T) { }, } - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) for _, tc := range testCases { 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) { t.Helper() - outputDir := testTempDir(t) + outputDir := ensure.TempDir(t) if len(flags) == 0 { 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 { - outputDir := testTempDir(t) + outputDir := ensure.TempDir(t) outputFile := filepath.Join(outputDir, "values.yaml") if err := ioutil.WriteFile(outputFile, []byte(data), 0755); err != nil { diff --git a/cmd/helm/path.go b/cmd/helm/path.go new file mode 100644 index 000000000..92987fe9d --- /dev/null +++ b/cmd/helm/path.go @@ -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 +} diff --git a/cmd/helm/plugin_install.go b/cmd/helm/plugin_install.go index 619bcda6d..94f7396a3 100644 --- a/cmd/helm/plugin_install.go +++ b/cmd/helm/plugin_install.go @@ -22,7 +22,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/cmd/helm/require" - "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin/installer" ) @@ -30,7 +29,6 @@ import ( type pluginInstallOptions struct { source string version string - home helmpath.Home } const pluginInstallDesc = ` @@ -60,14 +58,13 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command { func (o *pluginInstallOptions) complete(args []string) error { o.source = args[0] - o.home = settings.Home return nil } func (o *pluginInstallOptions) run(out io.Writer) error { 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 { return err } diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go index b9a355e69..929313e9e 100644 --- a/cmd/helm/plugin_list.go +++ b/cmd/helm/plugin_list.go @@ -25,35 +25,25 @@ import ( "helm.sh/helm/pkg/helmpath" ) -type pluginListOptions struct { - home helmpath.Home -} - func newPluginListCmd(out io.Writer) *cobra.Command { - o := &pluginListOptions{} cmd := &cobra.Command{ Use: "list", Short: "list installed Helm plugins", RunE: func(cmd *cobra.Command, args []string) error { - o.home = settings.Home - return o.run(out) + debug("pluginDirs: %s", helmpath.Plugins()) + plugins, err := findPlugins(helmpath.Plugins()) + if err != nil { + return err + } + + table := uitable.New() + table.AddRow("NAME", "VERSION", "DESCRIPTION") + for _, p := range plugins { + table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description) + } + fmt.Fprintln(out, table) + return nil }, } return cmd } - -func (o *pluginListOptions) run(out io.Writer) error { - debug("pluginDirs: %s", settings.PluginDirs()) - plugins, err := findPlugins(settings.PluginDirs()) - if err != nil { - return err - } - - table := uitable.New() - table.AddRow("NAME", "VERSION", "DESCRIPTION") - for _, p := range plugins { - table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description) - } - fmt.Fprintln(out, table) - return nil -} diff --git a/cmd/helm/plugin_remove.go b/cmd/helm/plugin_remove.go index e58a607f6..1d4ad67c2 100644 --- a/cmd/helm/plugin_remove.go +++ b/cmd/helm/plugin_remove.go @@ -30,7 +30,6 @@ import ( type pluginRemoveOptions struct { names []string - home helmpath.Home } 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") } o.names = args - o.home = settings.Home return nil } func (o *pluginRemoveOptions) run(out io.Writer) error { - debug("loading installed plugins from %s", settings.PluginDirs()) - plugins, err := findPlugins(settings.PluginDirs()) + debug("loading installed plugins from %s", helmpath.Plugins()) + plugins, err := findPlugins(helmpath.Plugins()) if err != nil { return err } diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index a2021db88..bde7b9c7f 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/pkg/helmpath" + "helm.sh/helm/pkg/helmpath/xdg" "helm.sh/helm/pkg/plugin" ) @@ -39,11 +40,11 @@ func TestManuallyProcessArgs(t *testing.T) { } expectKnown := []string{ - "--debug", "--context", "test1", "--home=/tmp", + "--debug", "--context", "test1", } expectUnknown := []string{ - "--foo", "bar", "command", + "--foo", "bar", "--home=/tmp", "command", } known, unknown := manuallyProcessArgs(input) @@ -64,10 +65,9 @@ func TestManuallyProcessArgs(t *testing.T) { func TestLoadPlugins(t *testing.T) { defer resetEnv()() - settings.Home = "testdata/helmhome" - - os.Setenv("HELM_HOME", settings.Home.String()) - hh := settings.Home + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome") out := bytes.NewBuffer(nil) cmd := &cobra.Command{} @@ -75,12 +75,13 @@ func TestLoadPlugins(t *testing.T) { envs := strings.Join([]string{ "fullenv", - hh.Plugins() + "/fullenv", - hh.Plugins(), - hh.String(), - hh.Repository(), - hh.RepositoryFile(), - hh.Cache(), + helmpath.Plugins() + "/fullenv", + helmpath.Plugins(), + helmpath.CachePath(), + helmpath.ConfigPath(), + helmpath.DataPath(), + helmpath.RepositoryFile(), + helmpath.RepositoryCache(), os.Args[0], }, "\n") @@ -94,7 +95,7 @@ func TestLoadPlugins(t *testing.T) { }{ {"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}}, {"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{}}, } @@ -134,7 +135,7 @@ func TestLoadPlugins(t *testing.T) { func TestLoadPlugins_HelmNoPlugins(t *testing.T) { defer resetEnv()() - settings.Home = "testdata/helmhome" + os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome") os.Setenv("HELM_NO_PLUGINS", "1") @@ -151,8 +152,8 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) { func TestSetupEnv(t *testing.T) { defer resetEnv()() name := "pequod" - settings.Home = helmpath.Home("testdata/helmhome") - base := filepath.Join(settings.Home.Plugins(), name) + os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome") + base := filepath.Join(helmpath.Plugins(), name) settings.Debug = true defer func() { settings.Debug = false @@ -165,13 +166,13 @@ func TestSetupEnv(t *testing.T) { }{ {"HELM_PLUGIN_NAME", name}, {"HELM_PLUGIN_DIR", base}, - {"HELM_PLUGIN", settings.Home.Plugins()}, {"HELM_DEBUG", "1"}, - {"HELM_HOME", settings.Home.String()}, - {"HELM_PATH_REPOSITORY", settings.Home.Repository()}, - {"HELM_PATH_REPOSITORY_FILE", settings.Home.RepositoryFile()}, - {"HELM_PATH_CACHE", settings.Home.Cache()}, - {"HELM_PATH_STARTER", settings.Home.Starters()}, + {"HELM_PATH_REPOSITORY_FILE", helmpath.RepositoryFile()}, + {"HELM_PATH_CACHE", helmpath.CachePath()}, + {"HELM_PATH_CONFIG", helmpath.ConfigPath()}, + {"HELM_PATH_DATA", helmpath.DataPath()}, + {"HELM_PATH_STARTER", helmpath.Starters()}, + {"HELM_PLUGIN", helmpath.Plugins()}, } { if got := os.Getenv(tt.name); got != tt.expect { t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got) diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go index 0c7c341fd..d7be8bb0e 100644 --- a/cmd/helm/plugin_update.go +++ b/cmd/helm/plugin_update.go @@ -31,7 +31,6 @@ import ( type pluginUpdateOptions struct { names []string - home helmpath.Home } 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") } o.names = args - o.home = settings.Home return nil } func (o *pluginUpdateOptions) run(out io.Writer) error { installer.Debug = settings.Debug - debug("loading installed plugins from %s", settings.PluginDirs()) - plugins, err := findPlugins(settings.PluginDirs()) + debug("loading installed plugins from %s", helmpath.Plugins()) + plugins, err := findPlugins(helmpath.Plugins()) if err != nil { return err } @@ -69,7 +67,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { for _, name := range o.names { 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)) } else { fmt.Fprintf(out, "Updated plugin: %s\n", name) @@ -84,7 +82,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { return nil } -func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { +func updatePlugin(p *plugin.Plugin) error { exactLocation, err := filepath.EvalSymlinks(p.Dir) if err != nil { return err @@ -94,7 +92,7 @@ func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { return err } - i, err := installer.FindSource(absExactLocation, home) + i, err := installer.FindSource(absExactLocation) if err != nil { return err } diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 5559fec61..311f6ffca 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -24,19 +24,27 @@ import ( "strings" "testing" + "helm.sh/helm/internal/test/ensure" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/repo/repotest" ) func TestPullCmd(t *testing.T) { defer resetEnv()() + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - hh := testHelmHome(t) - settings.Home = hh - - srv := repotest.NewServer(hh.String()) + srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*") + if err != nil { + t.Fatal(err) + } 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 { name 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 { - outdir := hh.Path("testout") + outdir := filepath.Join(helmpath.DataPath(), "testout") os.RemoveAll(outdir) 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) if err != nil { if tt.wantError { diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 9df0f946a..5168c9039 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -34,7 +34,6 @@ type repoAddOptions struct { url string username string password string - home helmpath.Home noupdate bool certFile string @@ -52,7 +51,6 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { o.name = args[0] o.url = args[1] - o.home = settings.Home return o.run(out) }, @@ -70,15 +68,15 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { } 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 } fmt.Fprintf(out, "%q has been added to your repositories\n", o.name) return nil } -func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { - f, err := repo.LoadFile(home.RepositoryFile()) +func addRepository(name, url, username, password string, certFile, keyFile, caFile string, noUpdate bool) error { + f, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { 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) } - cif := home.CacheIndex(name) c := repo.Entry{ Name: name, - Cache: cif, URL: url, Username: username, Password: password, @@ -104,11 +100,11 @@ func addRepository(name, url, username, password string, home helmpath.Home, cer 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) } f.Update(&c) - return f.WriteFile(home.RepositoryFile(), 0644) + return f.WriteFile(helmpath.RepositoryFile(), 0644) } diff --git a/cmd/helm/repo_add_test.go b/cmd/helm/repo_add_test.go index 9fd33390a..32ad7e1bc 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -21,6 +21,8 @@ import ( "os" "testing" + "helm.sh/helm/internal/test/ensure" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo/repotest" ) @@ -28,21 +30,35 @@ import ( func TestRepoAddCmd(t *testing.T) { 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 { t.Fatal(err) } - - defer func() { - srv.Stop() - os.RemoveAll(hh.String()) - }() - ensureTestHome(t, hh) - settings.Home = hh + defer srv.Stop() + + 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) + } + } tests := []cmdTestCase{{ 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", }} @@ -52,38 +68,52 @@ func TestRepoAddCmd(t *testing.T) { func TestRepoAdd(t *testing.T) { 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 { t.Fatal(err) } - - defer func() { - ts.Stop() - os.RemoveAll(hh.String()) - }() - ensureTestHome(t, hh) - settings.Home = hh + defer ts.Stop() + + 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) + } + } 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) } - f, err := repo.LoadFile(hh.RepositoryFile()) + f, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { t.Error(err) } 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) } - 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") } } diff --git a/cmd/helm/repo_index_test.go b/cmd/helm/repo_index_test.go index b66bc565a..110b9b892 100644 --- a/cmd/helm/repo_index_test.go +++ b/cmd/helm/repo_index_test.go @@ -23,12 +23,13 @@ import ( "path/filepath" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/repo" ) func TestRepoIndexCmd(t *testing.T) { - dir := testTempDir(t) + dir := ensure.TempDir(t) comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil { diff --git a/cmd/helm/repo_list.go b/cmd/helm/repo_list.go index b20e652dd..cea1733d3 100644 --- a/cmd/helm/repo_list.go +++ b/cmd/helm/repo_list.go @@ -29,39 +29,28 @@ import ( "helm.sh/helm/pkg/repo" ) -type repoListOptions struct { - home helmpath.Home -} - func newRepoListCmd(out io.Writer) *cobra.Command { - o := &repoListOptions{} - cmd := &cobra.Command{ Use: "list", Short: "list chart repositories", Args: require.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - o.home = settings.Home - return o.run(out) + f, err := repo.LoadFile(helmpath.RepositoryFile()) + if err != nil { + return err + } + if len(f.Repositories) == 0 { + return errors.New("no repositories to show") + } + table := uitable.New() + table.AddRow("NAME", "URL") + for _, re := range f.Repositories { + table.AddRow(re.Name, re.URL) + } + fmt.Fprintln(out, table) + return nil }, } return cmd } - -func (o *repoListOptions) run(out io.Writer) error { - f, err := repo.LoadFile(o.home.RepositoryFile()) - if err != nil { - return err - } - if len(f.Repositories) == 0 { - return errors.New("no repositories to show") - } - table := uitable.New() - table.AddRow("NAME", "URL") - for _, re := range f.Repositories { - table.AddRow(re.Name, re.URL) - } - fmt.Fprintln(out, table) - return nil -} diff --git a/cmd/helm/repo_remove.go b/cmd/helm/repo_remove.go index 75eb1a9bd..5600ab565 100644 --- a/cmd/helm/repo_remove.go +++ b/cmd/helm/repo_remove.go @@ -29,36 +29,22 @@ import ( "helm.sh/helm/pkg/repo" ) -type repoRemoveOptions struct { - name string - home helmpath.Home -} - func newRepoRemoveCmd(out io.Writer) *cobra.Command { - o := &repoRemoveOptions{} - cmd := &cobra.Command{ Use: "remove [NAME]", Aliases: []string{"rm"}, Short: "remove a chart repository", Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - o.name = args[0] - o.home = settings.Home - - return o.run(out) + return removeRepoLine(out, args[0]) }, } return cmd } -func (r *repoRemoveOptions) run(out io.Writer) error { - return removeRepoLine(out, r.name, r.home) -} - -func removeRepoLine(out io.Writer, name string, home helmpath.Home) error { - repoFile := home.RepositoryFile() +func removeRepoLine(out io.Writer, name string) error { + repoFile := helmpath.RepositoryFile() r, err := repo.LoadFile(repoFile) if err != nil { return err @@ -71,7 +57,7 @@ func removeRepoLine(out io.Writer, name string, home helmpath.Home) error { return err } - if err := removeRepoCache(name, home); err != nil { + if err := removeRepoCache(name); err != nil { return err } @@ -80,9 +66,9 @@ func removeRepoLine(out io.Writer, name string, home helmpath.Home) error { return nil } -func removeRepoCache(name string, home helmpath.Home) error { - if _, err := os.Stat(home.CacheIndex(name)); err == nil { - err = os.Remove(home.CacheIndex(name)) +func removeRepoCache(name string) error { + if _, err := os.Stat(helmpath.CacheIndex(name)); err == nil { + err = os.Remove(helmpath.CacheIndex(name)) if err != nil { return err } diff --git a/cmd/helm/repo_remove_test.go b/cmd/helm/repo_remove_test.go index 44bd20d70..451ef326c 100644 --- a/cmd/helm/repo_remove_test.go +++ b/cmd/helm/repo_remove_test.go @@ -22,6 +22,8 @@ import ( "strings" "testing" + "helm.sh/helm/internal/test/ensure" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo/repotest" ) @@ -29,44 +31,58 @@ import ( func TestRepoRemove(t *testing.T) { 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 { t.Fatal(err) } - - defer func() { - ts.Stop() - os.RemoveAll(hh.String()) - }() - ensureTestHome(t, hh) - settings.Home = hh + defer ts.Stop() + + 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) + } + } const testRepoName = "test-name" 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) } - if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", true); err != nil { + if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", true); err != nil { t.Error(err) } - mf, _ := os.Create(hh.CacheIndex(testRepoName)) + mf, _ := os.Create(helmpath.CacheIndex(testRepoName)) mf.Close() 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) } if !strings.Contains(b.String(), "has been removed") { 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) } - f, err := repo.LoadFile(hh.RepositoryFile()) + f, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { t.Error(err) } diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index a941c0867..b6c35f9a0 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -41,8 +41,7 @@ future releases. var errNoRepositories = errors.New("no repositories found. You must add one before updating") type repoUpdateOptions struct { - update func([]*repo.ChartRepository, io.Writer, helmpath.Home) - home helmpath.Home + update func([]*repo.ChartRepository, io.Writer) } func newRepoUpdateCmd(out io.Writer) *cobra.Command { @@ -55,7 +54,6 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { Long: updateDesc, Args: require.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - o.home = settings.Home return o.run(out) }, } @@ -63,7 +61,7 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { } func (o *repoUpdateOptions) run(out io.Writer) error { - f, err := repo.LoadFile(o.home.RepositoryFile()) + f, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { return err } @@ -80,18 +78,18 @@ func (o *repoUpdateOptions) run(out io.Writer) error { repos = append(repos, r) } - o.update(repos, out, o.home) + o.update(repos, out) 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...") var wg sync.WaitGroup for _, re := range repos { wg.Add(1) go func(re *repo.ChartRepository) { 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) } else { fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index 17213d9b3..bfe490305 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -23,8 +23,10 @@ import ( "strings" "testing" - "helm.sh/helm/pkg/getter" "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/repotest" ) @@ -32,20 +34,36 @@ import ( func TestUpdateCmd(t *testing.T) { defer resetEnv()() - hh := testHelmHome(t) - settings.Home = hh + ensure.HelmHome(t) + 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) // Instead of using the HTTP updater, we provide our own for this test. // 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 { fmt.Fprintln(out, re.Config.Name) } } o := &repoUpdateOptions{ update: updater, - home: hh, } if err := o.run(out); err != nil { t.Fatal(err) @@ -59,29 +77,25 @@ func TestUpdateCmd(t *testing.T) { func TestUpdateCharts(t *testing.T) { 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 { t.Fatal(err) } - - defer func() { - ts.Stop() - os.RemoveAll(hh.String()) - }() - ensureTestHome(t, hh) - settings.Home = hh + defer ts.Stop() r, err := repo.NewChartRepository(&repo.Entry{ - Name: "charts", - URL: ts.URL(), - Cache: hh.CacheIndex("charts"), + Name: "charts", + URL: ts.URL(), }, getter.All(settings)) if err != nil { t.Error(err) } b := bytes.NewBuffer(nil) - updateCharts([]*repo.ChartRepository{r}, b, hh) + updateCharts([]*repo.ChartRepository{r}, b) got := b.String() if strings.Contains(got, "Unable to get an update") { diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 9c97a4abd..d9af935d7 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io" + "strconv" "time" "github.com/spf13/cobra" @@ -31,32 +32,39 @@ const rollbackDesc = ` This command rolls back a release to a previous revision. The first argument of the rollback command is the name of a release, and the -second is a revision (version) number. To see revision numbers, run -'helm history RELEASE'. +second is a revision (version) number. If this argument is omitted, it will +roll back to the previous release. + +To see revision numbers, run 'helm history RELEASE'. ` func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewRollback(cfg) cmd := &cobra.Command{ - Use: "rollback [RELEASE] [REVISION]", + Use: "rollback [REVISION]", Short: "roll back a release to a previous revision", Long: rollbackDesc, - Args: require.ExactArgs(2), + Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - _, err := client.Run(args[0]) - if err != nil { + if len(args) > 1 { + ver, err := strconv.Atoi(args[1]) + if err != nil { + return fmt.Errorf("could not convert revision to a number: %v", err) + } + client.Version = ver + } + + if _, err := client.Run(args[0]); err != nil { return err } fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n") - return nil }, } f := cmd.Flags() - f.IntVar(&client.Version, "version", 0, "revision number to rollback to (default: rollback to previous release)") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") diff --git a/cmd/helm/rollback_test.go b/cmd/helm/rollback_test.go index 6283f6f20..de0f9b0f1 100644 --- a/cmd/helm/rollback_test.go +++ b/cmd/helm/rollback_test.go @@ -55,8 +55,13 @@ func TestRollbackCmd(t *testing.T) { golden: "output/rollback-wait.txt", rels: rels, }, { - name: "rollback a release without revision", - cmd: "rollback funny-honey", + name: "rollback a release without revision", + cmd: "rollback funny-honey", + golden: "output/rollback-no-revision.txt", + rels: rels, + }, { + name: "rollback a release without release name", + cmd: "rollback", golden: "output/rollback-no-args.txt", rels: rels, wantError: true, diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 18fc884bf..ff38eebd4 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -26,6 +26,7 @@ import ( "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/registry" ) @@ -100,7 +101,9 @@ Common actions from this point include: - helm list: list releases of charts 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_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. $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 // TODO: Move this elsewhere (first, settings.Init() must move) // 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) if err != nil { panic(err) @@ -145,7 +148,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string Resolver: registry.Resolver{ Resolver: resolver, }, - CacheRootDir: settings.Home.Registry(), + CacheRootDir: helmpath.Registry(), }) cmd.AddCommand( @@ -177,7 +180,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string newUpgradeCmd(actionConfig, out), newCompletionCmd(out), - newHomeCmd(out), + newPathCmd(out), newInitCmd(out), newPluginCmd(out), newVersionCmd(out), diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go index a8bec6354..5daca1eac 100644 --- a/cmd/helm/root_test.go +++ b/cmd/helm/root_test.go @@ -21,74 +21,77 @@ import ( "path/filepath" "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) { defer resetEnv()() tests := []struct { - name, args, home string - envars map[string]string + name, args, cachePath, configPath, dataPath string + envars map[string]string }{ { name: "defaults", args: "home", - home: filepath.Join(homedir.HomeDir(), ".helm"), }, { - name: "with --home set", - args: "--home /foo", - home: "/foo", + name: "with $XDG_CACHE_HOME set", + args: "home", + envars: map[string]string{xdg.CacheHomeEnvVar: "/bar"}, + cachePath: "/bar/helm", }, { - name: "subcommands with --home set", - args: "home --home /foo", - home: "/foo", + name: "with $XDG_CONFIG_HOME set", + args: "home", + envars: map[string]string{xdg.ConfigHomeEnvVar: "/bar"}, + configPath: "/bar/helm", }, { - name: "with $HELM_HOME set", - args: "home", - envars: map[string]string{"HELM_HOME": "/bar"}, - home: "/bar", - }, - { - name: "subcommands with $HELM_HOME set", - args: "home", - envars: map[string]string{"HELM_HOME": "/bar"}, - home: "/bar", - }, - { - name: "with $HELM_HOME and --home set", - args: "home --home /foo", - envars: map[string]string{"HELM_HOME": "/bar"}, - home: "/foo", + name: "with $XDG_DATA_HOME set", + args: "home", + envars: map[string]string{xdg.DataHomeEnvVar: "/bar"}, + dataPath: "/bar/helm", }, } - // ensure not set locally - os.Unsetenv("HELM_HOME") - for _, tt := range tests { 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 { os.Setenv(k, v) } - cmd, _, err := executeActionCommand(tt.args) - if err != nil { + if _, _, err := executeActionCommand(tt.args); err != nil { t.Fatalf("unexpected error: %s", err) } - if settings.Home.String() != tt.home { - t.Errorf("expected home %q, got %q", tt.home, settings.Home) + // NOTE(bacongobbler): we need to check here after calling ensure.HelmHome so we + // 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() - homeFlag = os.ExpandEnv(homeFlag) - if homeFlag != tt.home { - t.Errorf("expected home %q, got %q", tt.home, homeFlag) + if helmpath.DataPath() != tt.dataPath { + t.Errorf("expected data path %q, got %q", tt.dataPath, helmpath.DataPath()) } }) } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index cf3861ef0..ab6385fbb 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -42,8 +42,6 @@ Repositories are managed with 'helm repo' commands. const searchMaxScore = 25 type searchOptions struct { - helmhome helmpath.Home - versions bool regexp bool version string @@ -57,7 +55,6 @@ func newSearchCmd(out io.Writer) *cobra.Command { Short: "search for a keyword in charts", Long: searchDesc, RunE: func(cmd *cobra.Command, args []string) error { - o.helmhome = settings.Home 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) { // Load the repositories.yaml - rf, err := repo.LoadFile(o.helmhome.RepositoryFile()) + rf, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { return nil, err } @@ -149,7 +146,7 @@ func (o *searchOptions) buildIndex(out io.Writer) (*search.Index, error) { i := search.NewIndex() for _, re := range rf.Repositories { n := re.Name - f := o.helmhome.CacheIndex(n) + f := helmpath.CacheIndex(n) ind, err := repo.LoadIndexFile(f) if err != nil { // TODO should print to stderr diff --git a/cmd/helm/search_test.go b/cmd/helm/search_test.go index 380f87d34..ed73f8281 100644 --- a/cmd/helm/search_test.go +++ b/cmd/helm/search_test.go @@ -17,55 +17,58 @@ limitations under the License. package main import ( + "os" "testing" + + "helm.sh/helm/pkg/helmpath/xdg" ) func TestSearchCmd(t *testing.T) { defer resetEnv()() - setHome := func(cmd string) string { - return cmd + " --home=testdata/helmhome" - } + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome") tests := []cmdTestCase{{ name: "search for 'maria', expect one match", - cmd: setHome("search maria"), + cmd: "search maria", golden: "output/search-single.txt", }, { name: "search for 'alpine', expect two matches", - cmd: setHome("search alpine"), + cmd: "search alpine", golden: "output/search-multiple.txt", }, { name: "search for 'alpine' with versions, expect three matches", - cmd: setHome("search alpine --versions"), + cmd: "search alpine --versions", golden: "output/search-multiple-versions.txt", }, { 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", }, { 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", }, { 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", }, { 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", }, { name: "search for 'syzygy', expect no matches", - cmd: setHome("search syzygy"), + cmd: "search syzygy", golden: "output/search-not-found.txt", }, { 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", }, { name: "search for 'alp[', expect failure to compile regexp", - cmd: setHome("search alp[ --regexp"), + cmd: "search alp[ --regexp", wantError: true, }} runTestCmd(t, tests) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 9fcbc2b10..babfe0eac 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -25,6 +25,7 @@ import ( "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" + "helm.sh/helm/pkg/cli/values" ) const templateDesc = ` @@ -39,6 +40,7 @@ is done. func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var validate bool client := action.NewInstall(cfg) + valueOpts := &values.Options{} cmd := &cobra.Command{ Use: "template [NAME] [CHART]", @@ -50,7 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.ReleaseName = "RELEASE-NAME" client.Replace = true // Skip the name check client.ClientOnly = !validate - rel, err := runInstall(args, client, out) + rel, err := runInstall(args, client, valueOpts, out) if err != nil { return err } @@ -60,7 +62,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - addInstallFlags(f, client) + addInstallFlags(f, client, valueOpts) f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.BoolVar(&validate, "validate", false, "establish a connection to Kubernetes for schema validation") diff --git a/cmd/helm/testdata/helmhome/plugins/args/args.sh b/cmd/helm/testdata/helmhome/helm/plugins/args/args.sh similarity index 100% rename from cmd/helm/testdata/helmhome/plugins/args/args.sh rename to cmd/helm/testdata/helmhome/helm/plugins/args/args.sh diff --git a/cmd/helm/testdata/helmhome/plugins/args/plugin.yaml b/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/plugins/args/plugin.yaml rename to cmd/helm/testdata/helmhome/helm/plugins/args/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/plugins/echo/plugin.yaml b/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/plugins/echo/plugin.yaml rename to cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/plugins/env/plugin.yaml b/cmd/helm/testdata/helmhome/helm/plugins/env/plugin.yaml similarity index 62% rename from cmd/helm/testdata/helmhome/plugins/env/plugin.yaml rename to cmd/helm/testdata/helmhome/helm/plugins/env/plugin.yaml index c8ae40350..8b78a91c7 100644 --- a/cmd/helm/testdata/helmhome/plugins/env/plugin.yaml +++ b/cmd/helm/testdata/helmhome/helm/plugins/env/plugin.yaml @@ -1,4 +1,4 @@ name: env usage: "env stuff" description: "show the env" -command: "echo $HELM_HOME" +command: "echo $HELM_PATH_CONFIG" diff --git a/cmd/helm/testdata/helmhome/plugins/fullenv/fullenv.sh b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/fullenv.sh similarity index 64% rename from cmd/helm/testdata/helmhome/plugins/fullenv/fullenv.sh rename to cmd/helm/testdata/helmhome/helm/plugins/fullenv/fullenv.sh index d56b94b73..b53cb4a41 100755 --- a/cmd/helm/testdata/helmhome/plugins/fullenv/fullenv.sh +++ b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/fullenv.sh @@ -2,8 +2,9 @@ echo $HELM_PLUGIN_NAME echo $HELM_PLUGIN_DIR echo $HELM_PLUGIN -echo $HELM_HOME -echo $HELM_PATH_REPOSITORY -echo $HELM_PATH_REPOSITORY_FILE 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 diff --git a/cmd/helm/testdata/helmhome/plugins/fullenv/plugin.yaml b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/plugin.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/plugins/fullenv/plugin.yaml rename to cmd/helm/testdata/helmhome/helm/plugins/fullenv/plugin.yaml diff --git a/cmd/helm/testdata/helmhome/repository/repositories.yaml b/cmd/helm/testdata/helmhome/helm/repositories.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/repository/repositories.yaml rename to cmd/helm/testdata/helmhome/helm/repositories.yaml diff --git a/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml b/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml similarity index 100% rename from cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml rename to cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml diff --git a/cmd/helm/testdata/output/rollback-no-args.txt b/cmd/helm/testdata/output/rollback-no-args.txt index 3fde9d219..a1bc30b7a 100644 --- a/cmd/helm/testdata/output/rollback-no-args.txt +++ b/cmd/helm/testdata/output/rollback-no-args.txt @@ -1,3 +1,3 @@ -Error: "helm rollback" requires 2 arguments +Error: "helm rollback" requires at least 1 argument -Usage: helm rollback [RELEASE] [REVISION] [flags] +Usage: helm rollback [REVISION] [flags] diff --git a/cmd/helm/testdata/output/rollback-no-revision.txt b/cmd/helm/testdata/output/rollback-no-revision.txt new file mode 100644 index 000000000..ae3c6f1c4 --- /dev/null +++ b/cmd/helm/testdata/output/rollback-no-revision.txt @@ -0,0 +1 @@ +Rollback was a success! Happy Helming! diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 537660efd..87d279950 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -27,6 +27,7 @@ import ( "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" "helm.sh/helm/pkg/chart/loader" + "helm.sh/helm/pkg/cli/values" "helm.sh/helm/pkg/storage/driver" ) @@ -57,6 +58,7 @@ set for a key called 'foo', the 'newbar' value would take precedence: func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewUpgrade(cfg) + valueOpts := &values.Options{} cmd := &cobra.Command{ Use: "upgrade [RELEASE] [CHART]", @@ -71,7 +73,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Version = ">0.0.0-0" } - if err := client.ValueOptions.MergeValues(settings); err != nil { + vals, err := valueOpts.MergeValues(settings) + if err != nil { return err } @@ -89,7 +92,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) instClient := action.NewInstall(cfg) instClient.ChartPathOptions = client.ChartPathOptions - instClient.ValueOptions = client.ValueOptions instClient.DryRun = client.DryRun instClient.DisableHooks = client.DisableHooks instClient.Timeout = client.Timeout @@ -98,7 +100,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Namespace = client.Namespace instClient.Atomic = client.Atomic - _, err := runInstall(args, instClient, out) + _, err := runInstall(args, instClient, valueOpts, out) return err } } @@ -114,7 +116,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } - resp, err := client.Run(args[0], ch) + resp, err := client.Run(args[0], ch, vals) if err != nil { return errors.Wrap(err, "UPGRADE FAILED") } @@ -151,7 +153,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") f.IntVar(&client.MaxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") addChartPathOptionsFlags(f, &client.ChartPathOptions) - addValueOptionsFlags(f, &client.ValueOptions) + addValueOptionsFlags(f, valueOpts) return cmd } diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index e1115f174..0d544011e 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -23,6 +23,7 @@ import ( "strings" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chartutil" @@ -30,7 +31,7 @@ import ( ) func TestUpgradeCmd(t *testing.T) { - tmpChart := testTempDir(t) + tmpChart := ensure.TempDir(t) cfile := &chart.Chart{ Metadata: &chart.Metadata{ 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) { - tmpChart := testTempDir(t) + tmpChart := ensure.TempDir(t) configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") if err != nil { t.Fatalf("Error loading template yaml %v", err) diff --git a/internal/test/ensure/ensure.go b/internal/test/ensure/ensure.go new file mode 100644 index 000000000..8abc6c406 --- /dev/null +++ b/internal/test/ensure/ensure.go @@ -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 +} diff --git a/pkg/action/install.go b/pkg/action/install.go index 495545426..f2a5c96fe 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -20,7 +20,6 @@ import ( "bytes" "fmt" "io/ioutil" - "net/url" "os" "path" "path/filepath" @@ -30,7 +29,6 @@ import ( "github.com/Masterminds/sprig" "github.com/pkg/errors" - "sigs.k8s.io/yaml" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chartutil" @@ -38,13 +36,13 @@ import ( "helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/getter" + "helm.sh/helm/pkg/helmpath" kubefake "helm.sh/helm/pkg/kube/fake" "helm.sh/helm/pkg/release" "helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/storage" "helm.sh/helm/pkg/storage/driver" - "helm.sh/helm/pkg/strvals" "helm.sh/helm/pkg/version" ) @@ -68,7 +66,6 @@ type Install struct { cfg *Configuration ChartPathOptions - ValueOptions ClientOnly bool DryRun bool @@ -86,13 +83,6 @@ type Install struct { Atomic bool } -type ValueOptions struct { - ValueFiles []string - StringValues []string - Values []string - rawValues map[string]interface{} -} - type ChartPathOptions struct { CaFile string // --ca-file CertFile string // --cert-file @@ -115,7 +105,7 @@ func NewInstall(cfg *Configuration) *Install { // Run executes the installation // // If DryRun is set to true, this will prepare the release, but not install it -func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) { +func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { if err := i.availableName(); err != nil { return nil, err } @@ -128,7 +118,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) { i.cfg.Releases = storage.Init(driver.NewMemory()) } - if err := chartutil.ProcessDependencies(chrt, i.rawValues); err != nil { + if err := chartutil.ProcessDependencies(chrt, vals); err != nil { return nil, err } @@ -146,12 +136,12 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) { Namespace: i.Namespace, IsInstall: true, } - valuesToRender, err := chartutil.ToRenderValues(chrt, i.rawValues, options, caps) + valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) if err != nil { return nil, err } - rel := i.createRelease(chrt, i.rawValues) + rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir) // Even for errors, attach this if available @@ -538,7 +528,6 @@ OUTER: // Order of resolution: // - relative to current working directory // - if path is absolute or begins with '.', error out here -// - chart repos in $HELM_HOME // - URL // // If 'verify' is true, this will attempt to also verify the chart. @@ -562,16 +551,10 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s 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{ - HelmHome: settings.Home, - Out: os.Stdout, - Keyring: c.Keyring, - Getters: getter.All(settings), + Out: os.Stdout, + Keyring: c.Keyring, + Getters: getter.All(settings), Options: []getter.Option{ getter.WithBasicAuth(c.Username, c.Password), }, @@ -588,11 +571,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s name = chartURL } - if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { - os.MkdirAll(settings.Home.Archive(), 0744) + if _, err := os.Stat(helmpath.Archive()); os.IsNotExist(err) { + 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 { lname, err := filepath.Abs(filename) if err != nil { @@ -605,114 +588,3 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) } - -// MergeValues merges values from files specified via -f/--values and -// directly via --set or --set-string, marshaling them to YAML -func (v *ValueOptions) MergeValues(settings cli.EnvSettings) error { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range v.ValueFiles { - currentMap := map[string]interface{}{} - - bytes, err := readFile(filePath, settings) - if err != nil { - return err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return errors.Wrapf(err, "failed to parse %s", filePath) - } - // Merge with the previous map - base = mergeMaps(base, currentMap) - } - - // User specified a value via --set - for _, value := range v.Values { - if err := strvals.ParseInto(value, base); err != nil { - return errors.Wrap(err, "failed parsing --set data") - } - } - - // User specified a value via --set-string - for _, value := range v.StringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return errors.Wrap(err, "failed parsing --set-string data") - } - } - - v.rawValues = base - return nil -} - -func NewValueOptions(values map[string]interface{}) ValueOptions { - return ValueOptions{ - rawValues: values, - } -} - -// mergeValues merges source and destination map, preferring values from the source map -func mergeValues(dest, src map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}) - for k, v := range dest { - out[k] = v - } - for k, v := range src { - if _, ok := out[k]; !ok { - // If the key doesn't exist already, then just set the key to that value - } else if nextMap, ok := v.(map[string]interface{}); !ok { - // If it isn't another map, overwrite the value - } else if destMap, isMap := out[k].(map[string]interface{}); !isMap { - // Edge case: If the key exists in the destination, but isn't a map - // If the source map has a map for this key, prefer it - } else { - // If we got to this point, it is a map in both, so merge them - out[k] = mergeValues(destMap, nextMap) - continue - } - out[k] = v - } - return out -} - -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) - for k, v := range a { - out[k] = v - } - for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { - if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { - out[k] = mergeMaps(bv, v) - continue - } - } - } - out[k] = v - } - return out -} - -// readFile load a file from stdin, the local directory, or a remote file with a url. -func readFile(filePath string, settings cli.EnvSettings) ([]byte, error) { - if strings.TrimSpace(filePath) == "-" { - return ioutil.ReadAll(os.Stdin) - } - u, _ := url.Parse(filePath) - p := getter.All(settings) - - // FIXME: maybe someone handle other protocols like ftp. - getterConstructor, err := p.ByScheme(u.Scheme) - - if err != nil { - return ioutil.ReadFile(filePath) - } - - getter, err := getterConstructor(getter.WithURL(filePath)) - if err != nil { - return []byte{}, err - } - data, err := getter.Get(filePath) - return data.Bytes(), err -} diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 78c90a62e..2315fc4fa 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -22,7 +22,6 @@ import ( "log" "os" "path/filepath" - "reflect" "regexp" "testing" @@ -53,8 +52,8 @@ func installAction(t *testing.T) *Install { func TestInstallRelease(t *testing.T) { is := assert.New(t) instAction := installAction(t) - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart()) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -79,7 +78,7 @@ func TestInstallReleaseClientOnly(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ClientOnly = true - instAction.Run(buildChart()) // disregard output + instAction.Run(buildChart(), nil) // disregard output is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) @@ -88,8 +87,8 @@ func TestInstallReleaseClientOnly(t *testing.T) { func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" - instAction.rawValues = map[string]interface{}{} - _, err := instAction.Run(buildChart()) + vals := map[string]interface{}{} + _, err := instAction.Run(buildChart(), vals) if err == nil { t.Fatal("expected failure when no name is specified") } @@ -100,8 +99,8 @@ func TestInstallRelease_WithNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("note here"))) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withNotes("note here")), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -127,8 +126,8 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}"))) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -146,8 +145,8 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child")))) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -163,8 +162,8 @@ func TestInstallRelease_DryRun(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.DryRun = true - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart(withSampleTemplates())) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -189,8 +188,8 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction.ReleaseName = "no-hooks" instAction.cfg.Releases.Create(releaseStub()) - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart()) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(), vals) if err != nil { t.Fatalf("Failed install: %s", err) } @@ -206,8 +205,8 @@ func TestInstallRelease_FailedHooks(t *testing.T) { failer.WatchUntilReadyError = fmt.Errorf("Failed watch") instAction.cfg.KubeClient = failer - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart()) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(res.Info.Description, "failed post-install") is.Equal(release.StatusFailed, res.Info.Status) @@ -223,8 +222,8 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { instAction.cfg.Releases.Create(rel) instAction.ReleaseName = rel.Name - instAction.rawValues = map[string]interface{}{} - res, err := instAction.Run(buildChart()) + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(), vals) is.NoError(err) // This should have been auto-incremented @@ -239,14 +238,14 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { func TestInstallRelease_KubeVersion(t *testing.T) { is := assert.New(t) instAction := installAction(t) - instAction.rawValues = map[string]interface{}{} - _, err := instAction.Run(buildChart(withKube(">=0.0.0"))) + vals := map[string]interface{}{} + _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) is.NoError(err) // This should fail for a few hundred years instAction.ReleaseName = "should-fail" - instAction.rawValues = map[string]interface{}{} - _, err = instAction.Run(buildChart(withKube(">=99.0.0"))) + vals = map[string]interface{}{} + _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) is.Error(err) is.Contains(err.Error(), "chart requires kubernetesVersion") } @@ -259,9 +258,9 @@ func TestInstallRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer instAction.Wait = true - instAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - res, err := instAction.Run(buildChart()) + res, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -277,9 +276,9 @@ func TestInstallRelease_Atomic(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer instAction.Atomic = true - instAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - res, err := instAction.Run(buildChart()) + res, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "atomic") @@ -298,9 +297,9 @@ func TestInstallRelease_Atomic(t *testing.T) { failer.DeleteError = fmt.Errorf("uninstall fail") instAction.cfg.KubeClient = failer instAction.Atomic = true - instAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - _, err := instAction.Run(buildChart()) + _, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "uninstall fail") @@ -377,65 +376,10 @@ func TestNameTemplate(t *testing.T) { } } -func TestMergeValues(t *testing.T) { - nestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "stuff", - }, - } - anotherNestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - flatMap := map[string]interface{}{ - "foo": "bar", - "baz": "stuff", - } - anotherFlatMap := map[string]interface{}{ - "testing": "fun", - } - - testMap := mergeValues(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } - - testMap = mergeValues(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } - - testMap = mergeValues(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } - - testMap = mergeValues(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ - "testing": "fun", - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } -} - func TestInstallReleaseOutputDir(t *testing.T) { is := assert.New(t) instAction := installAction(t) - instAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} dir, err := ioutil.TempDir("", "output-dir") if err != nil { @@ -445,7 +389,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { instAction.OutputDir = dir - _, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate())) + _, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) if err != nil { t.Fatalf("Failed install: %s", err) } diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 5ec870570..d7ca8cd03 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -35,8 +35,6 @@ var errLintNoChart = errors.New("no chart found for linting (missing Chart.yaml) // // It provides the implementation of 'helm lint'. type Lint struct { - ValueOptions - Strict bool Namespace string } @@ -53,7 +51,7 @@ func NewLint() *Lint { } // Run executes 'helm Lint' against the given chart. -func (l *Lint) Run(paths []string) *LintResult { +func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { lowestTolerance := support.ErrorSev if l.Strict { lowestTolerance = support.WarningSev @@ -61,7 +59,7 @@ func (l *Lint) Run(paths []string) *LintResult { result := &LintResult{} for _, path := range paths { - if linter, err := lintChart(path, l.ValueOptions.rawValues, l.Namespace, l.Strict); err != nil { + if linter, err := lintChart(path, vals, l.Namespace, l.Strict); err != nil { if err == errLintNoChart { result.Errors = append(result.Errors, err) } diff --git a/pkg/action/package.go b/pkg/action/package.go index c3208b44b..5deb15a5f 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -36,8 +36,6 @@ import ( // // It provides the implementation of 'helm package'. type Package struct { - ValueOptions - Sign bool Key string Keyring string @@ -53,13 +51,13 @@ func NewPackage() *Package { } // Run executes 'helm package' against the given chart and returns the path to the packaged chart. -func (p *Package) Run(path string) (string, error) { +func (p *Package) Run(path string, vals map[string]interface{}) (string, error) { ch, err := loader.LoadDir(path) if err != nil { return "", err } - combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues) + combinedVals, err := chartutil.CoalesceValues(ch, vals) if err != nil { return "", err } diff --git a/pkg/action/pull.go b/pkg/action/pull.go index 812c00720..7304e994d 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -57,11 +57,10 @@ func (p *Pull) Run(chartRef string) (string, error) { var out strings.Builder c := downloader.ChartDownloader{ - HelmHome: p.Settings.Home, - Out: &out, - Keyring: p.Keyring, - Verify: downloader.VerifyNever, - Getters: getter.All(p.Settings), + Out: &out, + Keyring: p.Keyring, + Verify: downloader.VerifyNever, + Getters: getter.All(p.Settings), Options: []getter.Option{ getter.WithBasicAuth(p.Username, p.Password), }, diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 3d2731bfb..617cc02e7 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -39,7 +39,6 @@ type Upgrade struct { cfg *Configuration ChartPathOptions - ValueOptions Install bool Devel bool @@ -66,8 +65,8 @@ func NewUpgrade(cfg *Configuration) *Upgrade { } // Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) { - if err := chartutil.ProcessDependencies(chart, u.rawValues); err != nil { +func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { + if err := chartutil.ProcessDependencies(chart, vals); err != nil { return nil, err } @@ -79,7 +78,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) return nil, errors.Errorf("release name is invalid: %s", name) } u.cfg.Log("preparing upgrade for %s", name) - currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart) + currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) if err != nil { return nil, err } @@ -122,7 +121,7 @@ func validateReleaseName(releaseName string) error { } // prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) { +func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) { if chart == nil { return nil, nil, errMissingChart } @@ -134,7 +133,8 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele } // determine if values will be reused - if err := u.reuseValues(chart, currentRelease); err != nil { + vals, err = u.reuseValues(chart, currentRelease, vals) + if err != nil { return nil, nil, err } @@ -158,7 +158,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele if err != nil { return nil, nil, err } - valuesToRender, err := chartutil.ToRenderValues(chart, u.rawValues, options, caps) + valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps) if err != nil { return nil, nil, err } @@ -173,7 +173,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele Name: name, Namespace: currentRelease.Namespace, Chart: chart, - Config: u.rawValues, + Config: vals, Info: &release.Info{ FirstDeployed: currentRelease.Info.FirstDeployed, LastDeployed: Timestamper(), @@ -310,11 +310,11 @@ func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release // // This is skipped if the u.ResetValues flag is set, in which case the // request values are not altered. -func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error { +func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { if u.ResetValues { // If ResetValues is set, we completely ignore current.Config. u.cfg.Log("resetting values to the chart's original version") - return nil + return newVals, nil } // If the ReuseValues flag is set, we always copy the old values over the new config's values. @@ -324,21 +324,21 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) erro // We have to regenerate the old coalesced values: oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) if err != nil { - return errors.Wrap(err, "failed to rebuild old values") + return nil, errors.Wrap(err, "failed to rebuild old values") } - u.rawValues = chartutil.CoalesceTables(u.rawValues, current.Config) + newVals = chartutil.CoalesceTables(newVals, current.Config) chart.Values = oldVals - return nil + return newVals, nil } - if len(u.rawValues) == 0 && len(current.Config) > 0 { + if len(newVals) == 0 && len(current.Config) > 0 { u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) - u.rawValues = current.Config + newVals = current.Config } - return nil + return newVals, nil } func validateManifest(c kube.Interface, manifest []byte) error { diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 5e1913c15..81004e907 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -49,9 +49,9 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer upAction.Wait = true - upAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart()) + res, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -74,9 +74,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) { failer.WatchUntilReadyError = fmt.Errorf("arming key removed") upAction.cfg.KubeClient = failer upAction.Atomic = true - upAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart()) + res, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) is.Contains(err.Error(), "arming key removed") is.Contains(err.Error(), "atomic") @@ -99,9 +99,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) { failer.UpdateError = fmt.Errorf("update fail") upAction.cfg.KubeClient = failer upAction.Atomic = true - upAction.rawValues = map[string]interface{}{} + vals := map[string]interface{}{} - _, err := upAction.Run(rel.Name, buildChart()) + _, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) is.Contains(err.Error(), "update fail") is.Contains(err.Error(), "an error occurred while rolling back the release") @@ -141,8 +141,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { upAction.ReuseValues = true // setting newValues and upgrading - upAction.rawValues = newValues - res, err := upAction.Run(rel.Name, buildChart()) + res, err := upAction.Run(rel.Name, buildChart(), newValues) is.NoError(err) // Now make sure it is actually upgraded diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 03c63c119..1a54c169e 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -18,7 +18,7 @@ package chart // APIVersionV1 is the API version number for version 1. const APIVersionV1 = "v1" -// APIVersionV1 is the API version number for version 2. +// APIVersionV2 is the API version number for version 2. const APIVersionV2 = "v2" // Chart is a helm package that contains metadata, a default config, zero or more diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index 7308033c9..ca9eadb32 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -18,6 +18,7 @@ package loader import ( "bytes" + "log" "os" "path/filepath" "strings" @@ -103,6 +104,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { // Deprecated: requirements.yaml is deprecated use Chart.yaml. // We will handle it for you because we are nice people case f.Name == "requirements.yaml": + if c.Metadata.APIVersion != chart.APIVersionV1 { + log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") + } if c.Metadata == nil { c.Metadata = new(chart.Metadata) } diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 9e6697c40..0503d4edd 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -147,6 +147,19 @@ func TestLoadFileBackslash(t *testing.T) { verifyDependencies(t, c) } +func TestLoadV2WithReqs(t *testing.T) { + l, err := Loader("testdata/frobnitz.v2.reqs") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyDependencies(t, c) + verifyDependenciesLock(t, c) +} + func verifyChart(t *testing.T, c *chart.Chart) { t.Helper() if c.Name() == "" { diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore b/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml new file mode 100644 index 000000000..f3ab30291 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE b/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml new file mode 100644 index 000000000..79e0d65db --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..1c9dd5fa4 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 000000000..61cb62051 Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..21ae20aad --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz new file mode 100644 index 000000000..3190136b0 Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg b/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/cmd/helm/testdata/helmhome/repository/local/index.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt similarity index 100% rename from cmd/helm/testdata/helmhome/repository/local/index.yaml rename to pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml new file mode 100644 index 000000000..5eb0bc98b --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml @@ -0,0 +1,7 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index bb83304d6..194ea3135 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -24,21 +24,12 @@ package cli import ( "os" - "path/filepath" "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. type EnvSettings struct { - // Home is the local path to the Helm home directory. - Home helmpath.Home // Namespace is the namespace scope. Namespace string // KubeConfig is the path to the kubeconfig file. @@ -51,7 +42,6 @@ type EnvSettings struct { // AddFlags binds flags to the given 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.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") 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 var envMap = map[string]string{ "debug": "HELM_DEBUG", - "home": "HELM_HOME", "namespace": "HELM_NAMESPACE", } diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 06ddf73bd..a6204a1f7 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -22,8 +22,6 @@ import ( "testing" "github.com/spf13/pflag" - - "helm.sh/helm/pkg/helmpath" ) func TestEnvSettings(t *testing.T) { @@ -35,39 +33,31 @@ func TestEnvSettings(t *testing.T) { envars map[string]string // expected values - home, ns, kcontext, plugins string - debug bool + ns, kcontext string + debug bool }{ { - name: "defaults", - home: defaultHelmHome, - plugins: helmpath.Home(defaultHelmHome).Plugins(), - ns: "", + name: "defaults", + ns: "", }, { - name: "with flags set", - args: "--home /foo --debug --namespace=myns", - home: "/foo", - plugins: helmpath.Home("/foo").Plugins(), - ns: "myns", - debug: true, + name: "with flags set", + args: "--debug --namespace=myns", + ns: "myns", + debug: true, }, { - name: "with envvars set", - envars: map[string]string{"HELM_HOME": "/bar", "HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, - home: "/bar", - plugins: helmpath.Home("/bar").Plugins(), - ns: "yourns", - debug: true, + name: "with envvars set", + envars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, + ns: "yourns", + debug: true, }, { - name: "with flags and envvars set", - args: "--home /foo --debug --namespace=myns", - envars: map[string]string{"HELM_HOME": "/bar", "HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_PLUGIN": "glade"}, - home: "/foo", - plugins: "glade", - ns: "myns", - debug: true, + name: "with flags and envvars set", + args: "--debug --namespace=myns", + envars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, + ns: "myns", + debug: true, }, } @@ -87,12 +77,6 @@ func TestEnvSettings(t *testing.T) { 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 { t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) } diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go new file mode 100644 index 000000000..86e83ab76 --- /dev/null +++ b/pkg/cli/values/options.go @@ -0,0 +1,117 @@ +/* +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 values + +import ( + "io/ioutil" + "net/url" + "os" + "strings" + + "github.com/pkg/errors" + "sigs.k8s.io/yaml" + + "helm.sh/helm/pkg/cli" + "helm.sh/helm/pkg/getter" + "helm.sh/helm/pkg/strvals" +) + +type Options struct { + ValueFiles []string + StringValues []string + Values []string +} + +// MergeValues merges values from files specified via -f/--values and +// directly via --set or --set-string, marshaling them to YAML +func (opts *Options) MergeValues(settings cli.EnvSettings) (map[string]interface{}, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range opts.ValueFiles { + currentMap := map[string]interface{}{} + + bytes, err := readFile(filePath, settings) + if err != nil { + return nil, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", filePath) + } + // Merge with the previous map + base = mergeMaps(base, currentMap) + } + + // User specified a value via --set + for _, value := range opts.Values { + if err := strvals.ParseInto(value, base); err != nil { + return nil, errors.Wrap(err, "failed parsing --set data") + } + } + + // User specified a value via --set-string + for _, value := range opts.StringValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return nil, errors.Wrap(err, "failed parsing --set-string data") + } + } + + return base, nil +} + +func mergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = mergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} + +// readFile load a file from stdin, the local directory, or a remote file with a url. +func readFile(filePath string, settings cli.EnvSettings) ([]byte, error) { + if strings.TrimSpace(filePath) == "-" { + return ioutil.ReadAll(os.Stdin) + } + u, _ := url.Parse(filePath) + p := getter.All(settings) + + // FIXME: maybe someone handle other protocols like ftp. + getterConstructor, err := p.ByScheme(u.Scheme) + + if err != nil { + return ioutil.ReadFile(filePath) + } + + getter, err := getterConstructor(getter.WithURL(filePath)) + if err != nil { + return []byte{}, err + } + data, err := getter.Get(filePath) + return data.Bytes(), err +} diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go new file mode 100644 index 000000000..d988274bf --- /dev/null +++ b/pkg/cli/values/options_test.go @@ -0,0 +1,77 @@ +/* +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 values + +import ( + "reflect" + "testing" +) + +func TestMergeValues(t *testing.T) { + nestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "stuff", + }, + } + anotherNestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + flatMap := map[string]interface{}{ + "foo": "bar", + "baz": "stuff", + } + anotherFlatMap := map[string]interface{}{ + "testing": "fun", + } + + testMap := mergeMaps(flatMap, nestedMap) + equal := reflect.DeepEqual(testMap, nestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + } + + testMap = mergeMaps(nestedMap, flatMap) + equal = reflect.DeepEqual(testMap, flatMap) + if !equal { + t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + } + + testMap = mergeMaps(nestedMap, anotherNestedMap) + equal = reflect.DeepEqual(testMap, anotherNestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + } + + testMap = mergeMaps(anotherFlatMap, anotherNestedMap) + expectedMap := map[string]interface{}{ + "testing": "fun", + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + equal = reflect.DeepEqual(testMap, expectedMap) + if !equal { + t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + } +} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index ba24e570d..ea792626f 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -64,8 +64,6 @@ type ChartDownloader struct { Verify VerificationStrategy // Keyring is the keyring file used for verification. Keyring string - // HelmHome is the $HELM_HOME. - HelmHome helmpath.Home // Getter collection for the operation Getters getter.Providers // 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)) - rf, err := repo.LoadFile(c.HelmHome.RepositoryFile()) + rf, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { 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. - i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) + i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name)) if err != nil { 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 } - i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) + i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name)) if err != nil { return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 005a49554..e0e737b3a 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -16,20 +16,24 @@ limitations under the License. package downloader import ( - "io/ioutil" "net/http" "os" "path/filepath" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/helmpath" + "helm.sh/helm/pkg/helmpath/xdg" "helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo/repotest" ) func TestResolveChartRef(t *testing.T) { + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + tests := []struct { name, ref, expect, version string fail bool @@ -52,9 +56,8 @@ func TestResolveChartRef(t *testing.T) { } c := ChartDownloader{ - HelmHome: helmpath.Home("testdata/helmhome"), - Out: os.Stderr, - Getters: getter.All(cli.EnvSettings{}), + Out: os.Stderr, + Getters: getter.All(cli.EnvSettings{}), } for _, tt := range tests { @@ -102,97 +105,17 @@ func TestIsTar(t *testing.T) { } func TestDownloadTo(t *testing.T) { - tmp, err := ioutil.TempDir("", "helm-downloadto-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) + dest := helmpath.CachePath() - 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 - srv := repotest.NewServer(tmp) - defer srv.Stop() + // Set up a fake repo with basic auth enabled + srv := repotest.NewServer(helmpath.CachePath()) + srv.Stop() if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { t.Error(err) 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) { username, password, ok := r.BasicAuth() if !ok || username != "username" || password != "password" { @@ -201,20 +124,19 @@ func TestDownloadTo_WithOptions(t *testing.T) { })) srv.Start() defer srv.Stop() - if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { - t.Error(err) - return + if err := srv.CreateIndex(); err != nil { + t.Fatal(err) } + 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{}), + Out: os.Stderr, + Verify: VerifyAlways, + Keyring: "testdata/helm-test-key.pub", + Getters: getter.All(cli.EnvSettings{}), Options: []getter.Option{ getter.WithBasicAuth("username", "password"), }, @@ -222,8 +144,7 @@ func TestDownloadTo_WithOptions(t *testing.T) { cname := "/signtest-0.1.0.tgz" where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) if err != nil { - t.Error(err) - return + t.Fatal(err) } if expect := filepath.Join(dest, cname); where != expect { @@ -236,57 +157,34 @@ func TestDownloadTo_WithOptions(t *testing.T) { if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { t.Error(err) - return } } func TestDownloadTo_VerifyLater(t *testing.T) { - tmp, err := ioutil.TempDir("", "helm-downloadto-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - 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) - } - } + dest := helmpath.CachePath() // Set up a fake repo - srv := repotest.NewServer(tmp) - defer srv.Stop() - if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { - t.Error(err) - return + srv, err := repotest.NewTempServer("testdata/*.tgz*") + if err != nil { + t.Fatal(err) } + defer srv.Stop() if err := srv.LinkIndices(); err != nil { t.Fatal(err) } c := ChartDownloader{ - HelmHome: hh, - Out: os.Stderr, - Verify: VerifyLater, - Getters: getter.All(cli.EnvSettings{}), + Out: os.Stderr, + Verify: VerifyLater, + Getters: getter.All(cli.EnvSettings{}), } cname := "/signtest-0.1.0.tgz" where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) if err != nil { - t.Error(err) - return + t.Fatal(err) } 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 { - t.Error(err) - return + t.Fatal(err) } if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { - t.Error(err) - return + t.Fatal(err) } } func TestScanReposForURL(t *testing.T) { - hh := helmpath.Home("testdata/helmhome") + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + c := ChartDownloader{ - HelmHome: hh, - Out: os.Stderr, - Verify: VerifyLater, - Getters: getter.All(cli.EnvSettings{}), + Out: os.Stderr, + Verify: VerifyLater, + Getters: getter.All(cli.EnvSettings{}), } 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 { t.Fatal(err) } diff --git a/pkg/downloader/doc.go b/pkg/downloader/doc.go index c70b2f695..9588a7dfe 100644 --- a/pkg/downloader/doc.go +++ b/pkg/downloader/doc.go @@ -16,8 +16,8 @@ limitations under the License. /*Package downloader provides a library for downloading charts. This package contains various tools for downloading charts from repository -servers, and then storing them in Helm-specific directory structures (like -HELM_HOME). This library contains many functions that depend on a specific +servers, and then storing them in Helm-specific directory structures. This +library contains many functions that depend on a specific filesystem layout. */ package downloader diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 64f8cbbe8..d33d587ca 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -46,8 +46,6 @@ type Manager struct { Out io.Writer // ChartPath is the path to the unpacked base chart upon which this operates. ChartPath string - // HelmHome is the $HELM_HOME directory - HelmHome helmpath.Home // Verification indicates whether the chart should be verified. Verify VerificationStrategy // 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. 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) } @@ -231,11 +229,10 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { } dl := ChartDownloader{ - Out: m.Out, - Verify: m.Verify, - Keyring: m.Keyring, - HelmHome: m.HelmHome, - Getters: m.Getters, + Out: m.Out, + Verify: m.Verify, + Keyring: m.Keyring, + Getters: m.Getters, Options: []getter.Option{ 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. func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { - rf, err := repo.LoadFile(m.HelmHome.RepositoryFile()) + rf, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { 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. 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 { 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. func (m *Manager) UpdateRepositories() error { - rf, err := repo.LoadFile(m.HelmHome.RepositoryFile()) + rf, err := repo.LoadFile(helmpath.RepositoryFile()) if err != nil { return err } @@ -440,7 +437,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { } wg.Add(1) 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) } else { 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). func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { indices := map[string]*repo.ChartRepository{} - repoyaml := m.HelmHome.RepositoryFile() + repoyaml := helmpath.RepositoryFile() // Load repositories.yaml file rf, err := repo.LoadFile(repoyaml) @@ -562,8 +559,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err for _, re := range rf.Repositories { lname := re.Name - cacheindex := m.HelmHome.CacheIndex(lname) - index, err := repo.LoadIndexFile(cacheindex) + index, err := repo.LoadIndexFile(helmpath.CacheIndex(lname)) if err != nil { return indices, err } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 8f1c74cf6..d015e5b3e 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -17,11 +17,12 @@ package downloader import ( "bytes" + "os" "reflect" "testing" "helm.sh/helm/pkg/chart" - "helm.sh/helm/pkg/helmpath" + "helm.sh/helm/pkg/helmpath/xdg" ) func TestVersionEquals(t *testing.T) { @@ -63,10 +64,12 @@ func TestNormalizeURL(t *testing.T) { } func TestFindChartURL(t *testing.T) { + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + b := bytes.NewBuffer(nil) m := &Manager{ - Out: b, - HelmHome: helmpath.Home("testdata/helmhome"), + Out: b, } repos, err := m.loadChartRepositories() if err != nil { @@ -93,10 +96,12 @@ func TestFindChartURL(t *testing.T) { } func TestGetRepoNames(t *testing.T) { + os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") + os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") + b := bytes.NewBuffer(nil) m := &Manager{ - Out: b, - HelmHome: helmpath.Home("testdata/helmhome"), + Out: b, } tests := []struct { name string diff --git a/pkg/downloader/testdata/helmhome/repository/repositories.yaml b/pkg/downloader/testdata/helmhome/helm/repositories.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/repositories.yaml rename to pkg/downloader/testdata/helmhome/helm/repositories.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/kubernetes-charts-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/kubernetes-charts-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/malformed-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/malformed-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/malformed-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/malformed-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-basicauth-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-basicauth-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-basicauth-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-basicauth-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-https-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-https-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-https-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-https-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-querystring-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-querystring-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-relative-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-relative-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-relative-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml b/pkg/downloader/testdata/helmhome/helm/repository/testing-relative-trailing-slash-index.yaml similarity index 100% rename from pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml rename to pkg/downloader/testdata/helmhome/helm/repository/testing-relative-trailing-slash-index.yaml diff --git a/pkg/downloader/testdata/helmhome/repository/local/index.yaml b/pkg/downloader/testdata/helmhome/repository/local/index.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go index 41ed1079c..627d8d2ec 100644 --- a/pkg/getter/getter_test.go +++ b/pkg/getter/getter_test.go @@ -18,6 +18,9 @@ package getter import ( "os" "testing" + + "helm.sh/helm/pkg/cli" + "helm.sh/helm/pkg/helmpath/xdg" ) func TestProvider(t *testing.T) { @@ -50,13 +53,9 @@ func TestProviders(t *testing.T) { } func TestAll(t *testing.T) { - oldhh := os.Getenv("HELM_HOME") - defer os.Setenv("HELM_HOME", oldhh) - os.Setenv("HELM_HOME", "") - - env := hh(false) + os.Setenv(xdg.DataHomeEnvVar, "testdata") - all := All(env) + all := All(cli.EnvSettings{}) if len(all) != 3 { 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) { - oldhh := os.Getenv("HELM_HOME") - defer os.Setenv("HELM_HOME", oldhh) - os.Setenv("HELM_HOME", "") + os.Setenv(xdg.DataHomeEnvVar, "testdata") - env := hh(false) - if _, err := ByScheme("test", env); err != nil { + if _, err := ByScheme("test", cli.EnvSettings{}); err != nil { t.Error(err) } - if _, err := ByScheme("https", env); err != nil { + if _, err := ByScheme("https", cli.EnvSettings{}); err != nil { t.Error(err) } } diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index 0b2b0fc8a..78e44ea8d 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -25,13 +25,14 @@ import ( "github.com/pkg/errors" "helm.sh/helm/pkg/cli" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/plugin" ) // collectPlugins scans for getter plugins. // This will load plugins according to the cli. func collectPlugins(settings cli.EnvSettings) (Providers, error) { - plugins, err := plugin.FindPlugins(settings.PluginDirs()) + plugins, err := plugin.FindPlugins(helmpath.Plugins()) if err != nil { return nil, err } diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index 8388cc22f..e5fdb88a8 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -17,34 +17,18 @@ package getter import ( "os" - "path/filepath" "runtime" "strings" "testing" "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) { - // Reset HELM HOME to testdata. - oldhh := os.Getenv("HELM_HOME") - defer os.Setenv("HELM_HOME", oldhh) - os.Setenv("HELM_HOME", "") + os.Setenv(xdg.DataHomeEnvVar, "testdata") - env := hh(false) + env := cli.EnvSettings{} p, err := collectPlugins(env) if err != nil { t.Fatal(err) @@ -72,11 +56,9 @@ func TestPluginGetter(t *testing.T) { t.Skip("TODO: refactor this test to work on windows") } - oldhh := os.Getenv("HELM_HOME") - defer os.Setenv("HELM_HOME", oldhh) - os.Setenv("HELM_HOME", "") + os.Setenv(xdg.DataHomeEnvVar, "testdata") - env := hh(false) + env := cli.EnvSettings{} pg := NewPluginGetter("echo", env, "test", ".") g, err := pg() if err != nil { @@ -104,7 +86,7 @@ func TestPluginSubCommands(t *testing.T) { defer os.Setenv("HELM_HOME", oldhh) os.Setenv("HELM_HOME", "") - env := hh(false) + env := cli.EnvSettings{} pg := NewPluginGetter("echo -n", env, "test", ".") g, err := pg() if err != nil { diff --git a/pkg/getter/testdata/plugins/testgetter/get.sh b/pkg/getter/testdata/helm/plugins/testgetter/get.sh similarity index 100% rename from pkg/getter/testdata/plugins/testgetter/get.sh rename to pkg/getter/testdata/helm/plugins/testgetter/get.sh diff --git a/pkg/getter/testdata/plugins/testgetter/plugin.yaml b/pkg/getter/testdata/helm/plugins/testgetter/plugin.yaml similarity index 100% rename from pkg/getter/testdata/plugins/testgetter/plugin.yaml rename to pkg/getter/testdata/helm/plugins/testgetter/plugin.yaml diff --git a/pkg/getter/testdata/plugins/testgetter2/get.sh b/pkg/getter/testdata/helm/plugins/testgetter2/get.sh similarity index 100% rename from pkg/getter/testdata/plugins/testgetter2/get.sh rename to pkg/getter/testdata/helm/plugins/testgetter2/get.sh diff --git a/pkg/getter/testdata/plugins/testgetter2/plugin.yaml b/pkg/getter/testdata/helm/plugins/testgetter2/plugin.yaml similarity index 100% rename from pkg/getter/testdata/plugins/testgetter2/plugin.yaml rename to pkg/getter/testdata/helm/plugins/testgetter2/plugin.yaml diff --git a/pkg/getter/testdata/repository/local/index.yaml b/pkg/getter/testdata/helm/repository/local/index.yaml similarity index 100% rename from pkg/getter/testdata/repository/local/index.yaml rename to pkg/getter/testdata/helm/repository/local/index.yaml diff --git a/pkg/getter/testdata/repository/repositories.yaml b/pkg/getter/testdata/helm/repository/repositories.yaml similarity index 100% rename from pkg/getter/testdata/repository/repositories.yaml rename to pkg/getter/testdata/helm/repository/repositories.yaml diff --git a/pkg/helmpath/helmhome.go b/pkg/helmpath/helmhome.go deleted file mode 100644 index 7641225c7..000000000 --- a/pkg/helmpath/helmhome.go +++ /dev/null @@ -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") -} diff --git a/pkg/helmpath/helmhome_unix_test.go b/pkg/helmpath/helmhome_unix_test.go deleted file mode 100644 index ea4a824df..000000000 --- a/pkg/helmpath/helmhome_unix_test.go +++ /dev/null @@ -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") - } -} diff --git a/pkg/helmpath/home.go b/pkg/helmpath/home.go new file mode 100644 index 000000000..7de824dc5 --- /dev/null +++ b/pkg/helmpath/home.go @@ -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") +} diff --git a/pkg/helmpath/home_unix_test.go b/pkg/helmpath/home_unix_test.go new file mode 100644 index 000000000..0c933a862 --- /dev/null +++ b/pkg/helmpath/home_unix_test.go @@ -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") +} diff --git a/pkg/helmpath/helmhome_windows_test.go b/pkg/helmpath/home_windows_test.go similarity index 51% rename from pkg/helmpath/helmhome_windows_test.go rename to pkg/helmpath/home_windows_test.go index 71bc8ac44..d0258094e 100644 --- a/pkg/helmpath/helmhome_windows_test.go +++ b/pkg/helmpath/home_windows_test.go @@ -16,23 +16,34 @@ package helmpath import ( + "os" "testing" + + "helm.sh/helm/pkg/helmpath/xdg" ) 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) { if a != b { t.Errorf("Expected %q, got %q", b, a) } } - 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") + isEq(t, CachePath(), "c:\\helm") + isEq(t, ConfigPath(), "d:\\helm") + isEq(t, DataPath(), "e:\\helm") + isEq(t, RepositoryFile(), "d:\\helm\\repositories.yaml") + isEq(t, RepositoryCache(), "c:\\helm\\repository") + isEq(t, CacheIndex("t"), "c:\\helm\\repository\\t-index.yaml") + isEq(t, CacheIndex(""), "c:\\helm\\repository\\index.yaml") + 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") } diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go new file mode 100644 index 000000000..842f93923 --- /dev/null +++ b/pkg/helmpath/lazypath.go @@ -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) +} diff --git a/pkg/helmpath/lazypath_darwin.go b/pkg/helmpath/lazypath_darwin.go new file mode 100644 index 000000000..e112b8337 --- /dev/null +++ b/pkg/helmpath/lazypath_darwin.go @@ -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") +} diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go new file mode 100644 index 000000000..bf222ec4b --- /dev/null +++ b/pkg/helmpath/lazypath_darwin_test.go @@ -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)) + } +} diff --git a/pkg/helmpath/lazypath_unix.go b/pkg/helmpath/lazypath_unix.go new file mode 100644 index 000000000..b4eae9f66 --- /dev/null +++ b/pkg/helmpath/lazypath_unix.go @@ -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") +} diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go new file mode 100644 index 000000000..f465ead75 --- /dev/null +++ b/pkg/helmpath/lazypath_unix_test.go @@ -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)) + } +} diff --git a/pkg/helmpath/lazypath_windows.go b/pkg/helmpath/lazypath_windows.go new file mode 100644 index 000000000..38d9dec9a --- /dev/null +++ b/pkg/helmpath/lazypath_windows.go @@ -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") +} diff --git a/pkg/helmpath/lazypath_windows_test.go b/pkg/helmpath/lazypath_windows_test.go new file mode 100644 index 000000000..92f5a7be9 --- /dev/null +++ b/pkg/helmpath/lazypath_windows_test.go @@ -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)) + } +} diff --git a/pkg/helmpath/xdg/xdg.go b/pkg/helmpath/xdg/xdg.go new file mode 100644 index 000000000..010e1138b --- /dev/null +++ b/pkg/helmpath/xdg/xdg.go @@ -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" +) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 93f090af2..aadbc933f 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -29,7 +29,6 @@ import ( "github.com/pkg/errors" batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -283,12 +282,17 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing target configuration") } - // While different objects need different merge types, the parent function - // that calls this does not try to create a patch when the data (first - // returned object) is nil. We can skip calculating the merge type as - // the returned merge type is ignored. - if apiequality.Semantic.DeepEqual(oldData, newData) { - return nil, types.StrategicMergePatchType, nil + // Fetch the current object for the three way merge + helper := resource.NewHelper(target.Client, target.Mapping) + currentObj, err := helper.Get(target.Namespace, target.Name, target.Export) + if err != nil && !apierrors.IsNotFound(err) { + return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name) + } + + // Even if currentObj is nil (because it was not found), it will marshal just fine + currentData, err := json.Marshal(currentObj) + if err != nil { + return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing live configuration") } // Get a versioned object @@ -303,7 +307,13 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P patch, err := jsonpatch.CreateMergePatch(oldData, newData) return patch, types.MergePatchType, err } - patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) + + patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) + if err != nil { + return nil, types.StrategicMergePatchType, errors.Wrap(err, "unable to create patch metadata from object") + } + + patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true) return patch, types.StrategicMergePatchType, err } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index c9da5cc57..b097c4782 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -119,6 +119,17 @@ func TestUpdate(t *testing.T) { return newResponse(200, &listA.Items[0]) case p == "/namespaces/default/pods/otter" && m == "GET": return newResponse(200, &listA.Items[1]) + case p == "/namespaces/default/pods/otter" && m == "PATCH": + data, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatalf("could not dump request: %s", err) + } + req.Body.Close() + expected := `{}` + if string(data) != expected { + t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) + } + return newResponse(200, &listB.Items[0]) case p == "/namespaces/default/pods/dolphin" && m == "GET": return newResponse(404, notFoundBody()) case p == "/namespaces/default/pods/starfish" && m == "PATCH": @@ -165,10 +176,12 @@ func TestUpdate(t *testing.T) { // t.Fatal(err) // } expectedActions := []string{ + "/namespaces/default/pods/starfish:GET", "/namespaces/default/pods/starfish:GET", "/namespaces/default/pods/starfish:PATCH", "/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:GET", + "/namespaces/default/pods/otter:PATCH", "/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods:POST", "/namespaces/default/pods/squid:DELETE", diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 962a9ca41..0f2c7ca0d 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -35,12 +35,12 @@ const goodChartDir = "rules/testdata/goodone" func TestBadChart(t *testing.T) { m := All(badChartDir, values, namespace, strict).Messages - if len(m) != 6 { + if len(m) != 8 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) } // There should be one INFO, 2 WARNINGs and one ERROR messages, check for them - var i, w, e, e2, e3, e4 bool + var i, w, e, e2, e3, e4, e5, e6 bool for _, msg := range m { if msg.Severity == support.InfoSev { if strings.Contains(msg.Err.Error(), "icon is recommended") { @@ -66,9 +66,17 @@ func TestBadChart(t *testing.T) { if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { e4 = true } + + if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { + e5 = true + } + + if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { + e6 = true + } } } - if !e || !e2 || !e3 || !e4 || !w || !i { + if !e || !e2 || !e3 || !e4 || !e5 || !e6 || !w || !i { t.Errorf("Didn't find all the expected errors, got %#v", m) } } diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index da0675868..91ad7543f 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -55,6 +55,8 @@ func Chartfile(linter *support.Linter) { linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) } func validateChartYamlNotDirectory(chartPath string) error { @@ -92,7 +94,7 @@ func validateChartAPIVersion(cf *chart.Metadata) error { return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") } - if cf.APIVersion != "v1" && cf.APIVersion != "v2" { + if cf.APIVersion != chart.APIVersionV1 && cf.APIVersion != chart.APIVersionV2 { return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion) } @@ -158,3 +160,17 @@ func validateChartIconURL(cf *chart.Metadata) error { } return nil } + +func validateChartDependencies(cf *chart.Metadata) error { + if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV2 { + return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) + } + return nil +} + +func validateChartType(cf *chart.Metadata) error { + if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 { + return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) + } + return nil +} diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index 4e71b860a..6bf33bde1 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -209,8 +209,8 @@ func TestChartfile(t *testing.T) { Chartfile(&linter) msgs := linter.Messages - if len(msgs) != 5 { - t.Errorf("Expected 4 errors, got %d", len(msgs)) + if len(msgs) != 7 { + t.Errorf("Expected 7 errors, got %d", len(msgs)) } if !strings.Contains(msgs[0].Err.Error(), "name is required") { @@ -226,11 +226,19 @@ func TestChartfile(t *testing.T) { } if !strings.Contains(msgs[3].Err.Error(), "version 0.0.0 is less than or equal to 0") { - t.Errorf("Unexpected message 3: %s", msgs[2].Err) + t.Errorf("Unexpected message 3: %s", msgs[3].Err) } if !strings.Contains(msgs[4].Err.Error(), "icon is recommended") { - t.Errorf("Unexpected message 4: %s", msgs[3].Err) + t.Errorf("Unexpected message 4: %s", msgs[4].Err) + } + + if !strings.Contains(msgs[5].Err.Error(), "chart type is not valid in apiVersion") { + t.Errorf("Unexpected message 5: %s", msgs[5].Err) + } + + if !strings.Contains(msgs[6].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { + t.Errorf("Unexpected message 6: %s", msgs[6].Err) } } diff --git a/pkg/lint/rules/testdata/badchartfile/Chart.yaml b/pkg/lint/rules/testdata/badchartfile/Chart.yaml index dbb4a1501..704b745ed 100644 --- a/pkg/lint/rules/testdata/badchartfile/Chart.yaml +++ b/pkg/lint/rules/testdata/badchartfile/Chart.yaml @@ -1,3 +1,11 @@ description: A Helm chart for Kubernetes version: 0.0.0 home: "" +type: application +dependencies: +- name: mariadb + version: 5.x.x + repository: https://kubernetes-charts.storage.googleapis.com/ + condition: mariadb.enabled + tags: + - database diff --git a/pkg/plugin/installer/base.go b/pkg/plugin/installer/base.go index e22c2e10e..e9840b5b9 100644 --- a/pkg/plugin/installer/base.go +++ b/pkg/plugin/installer/base.go @@ -25,15 +25,13 @@ import ( type base struct { // Source is the reference to a plugin Source string - // HelmHome is the $HELM_HOME directory - HelmHome helmpath.Home } -func newBase(source string, home helmpath.Home) base { - return base{source, home} +func newBase(source string) base { + 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 { debug("symlinking %s to %s", from, b.Path()) return os.Symlink(from, b.Path()) @@ -44,5 +42,5 @@ func (b *base) Path() string { if b.Source == "" { return "" } - return filepath.Join(b.HelmHome.Plugins(), filepath.Base(b.Source)) + return filepath.Join(helmpath.Plugins(), filepath.Base(b.Source)) } diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go index 80ade67e0..d3294b3b3 100644 --- a/pkg/plugin/installer/http_installer.go +++ b/pkg/plugin/installer/http_installer.go @@ -67,7 +67,7 @@ func NewExtractor(source string) (Extractor, error) { } // 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) if err != nil { @@ -90,9 +90,9 @@ func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) } i := &HTTPInstaller{ - CacheDir: home.Path("cache", "plugins", key), + CacheDir: filepath.Join(helmpath.PluginCache(), key), PluginName: stripPluginName(filepath.Base(source)), - base: newBase(source, home), + base: newBase(source), extractor: extractor, getter: get, } @@ -112,7 +112,8 @@ func stripPluginName(name string) string { 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. func (i *HTTPInstaller) Install() error { @@ -156,7 +157,7 @@ func (i HTTPInstaller) Path() string { if i.base.Source == "" { return "" } - return filepath.Join(i.base.HelmHome.Plugins(), i.PluginName) + return filepath.Join(helmpath.Plugins(), i.PluginName) } // Extract extracts compressed archives diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index 04c6ef5e9..55e915b4e 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -18,12 +18,13 @@ package installer // import "helm.sh/helm/pkg/plugin/installer" import ( "bytes" "encoding/base64" - "io/ioutil" "os" + "path/filepath" "testing" "github.com/pkg/errors" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/helmpath" ) @@ -57,18 +58,14 @@ func TestStripName(t *testing.T) { func TestHTTPInstaller(t *testing.T) { source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" - hh, err := ioutil.TempDir("", "helm-home-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(hh) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - home := helmpath.Home(hh) - if err := os.MkdirAll(home.Plugins(), 0755); err != nil { - t.Fatalf("Could not create %s: %s", home.Plugins(), err) + if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) } - i, err := NewForSource(source, "0.0.1", home) + i, err := NewForSource(source, "0.0.1") if err != nil { t.Errorf("unexpected error: %s", err) } @@ -93,8 +90,8 @@ func TestHTTPInstaller(t *testing.T) { if err := Install(i); err != nil { t.Error(err) } - if i.Path() != home.Path("plugins", "fake-plugin") { - t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") { + t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) } // Install again to test plugin exists error @@ -108,18 +105,14 @@ func TestHTTPInstaller(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) { source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" - hh, err := ioutil.TempDir("", "helm-home-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(hh) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - home := helmpath.Home(hh) - if err := os.MkdirAll(home.Plugins(), 0755); err != nil { - t.Fatalf("Could not create %s: %s", home.Plugins(), err) + if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) } - i, err := NewForSource(source, "0.0.2", home) + i, err := NewForSource(source, "0.0.2") if err != nil { t.Errorf("unexpected error: %s", err) } @@ -144,18 +137,14 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) { source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" - hh, err := ioutil.TempDir("", "helm-home-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(hh) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - home := helmpath.Home(hh) - if err := os.MkdirAll(home.Plugins(), 0755); err != nil { - t.Fatalf("Could not create %s: %s", home.Plugins(), err) + if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) } - i, err := NewForSource(source, "0.0.1", home) + i, err := NewForSource(source, "0.0.1") if err != nil { t.Errorf("unexpected error: %s", err) } @@ -180,8 +169,8 @@ func TestHTTPInstallerUpdate(t *testing.T) { if err := Install(i); err != nil { t.Error(err) } - if i.Path() != home.Path("plugins", "fake-plugin") { - t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") { + t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) } // Update plugin, should fail because it is not implemented diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index bd5fbc814..9303d7e19 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -23,8 +23,6 @@ import ( "strings" "github.com/pkg/errors" - - "helm.sh/helm/pkg/helmpath" ) // ErrMissingMetadata indicates that plugin.yaml is missing. @@ -35,18 +33,18 @@ var Debug bool // Installer provides an interface for installing helm client plugins. type Installer interface { - // Install adds a plugin to $HELM_HOME. + // Install adds a plugin. Install() error // Path is the directory of the installed plugin. Path() string - // Update updates a plugin to $HELM_HOME. + // Update updates a plugin. Update() error } -// Install installs a plugin to $HELM_HOME. +// Install installs a plugin. func Install(i Installer) error { 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) { @@ -56,7 +54,7 @@ func Install(i Installer) error { return i.Install() } -// Update updates a plugin in $HELM_HOME. +// Update updates a plugin. func Update(i Installer) error { if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { 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. -func NewForSource(source, version string, home helmpath.Home) (Installer, error) { +func NewForSource(source, version string) (Installer, error) { // Check if source is a local directory if isLocalReference(source) { - return NewLocalInstaller(source, home) + return NewLocalInstaller(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. -func FindSource(location string, home helmpath.Home) (Installer, error) { - installer, err := existingVCSRepo(location, home) +func FindSource(location string) (Installer, error) { + installer, err := existingVCSRepo(location) if err != nil && err.Error() == "Cannot detect VCS" { return installer, errors.New("cannot get information about plugin source") } diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go index 18ed827e8..a3743d4da 100644 --- a/pkg/plugin/installer/local_installer.go +++ b/pkg/plugin/installer/local_installer.go @@ -19,8 +19,6 @@ import ( "path/filepath" "github.com/pkg/errors" - - "helm.sh/helm/pkg/helmpath" ) // LocalInstaller installs plugins from the filesystem. @@ -29,18 +27,18 @@ type LocalInstaller struct { } // 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) if err != nil { return nil, errors.Wrap(err, "unable to get absolute path to plugin") } i := &LocalInstaller{ - base: newBase(src, home), + base: newBase(src), } return i, nil } -// Install creates a symlink to the plugin directory in $HELM_HOME. +// Install creates a symlink to the plugin directory. // // Implements Installer. func (i *LocalInstaller) Install() error { diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go index 9d8627d7c..828b5e3ce 100644 --- a/pkg/plugin/installer/local_installer_test.go +++ b/pkg/plugin/installer/local_installer_test.go @@ -21,22 +21,15 @@ import ( "path/filepath" "testing" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/helmpath" ) var _ Installer = new(LocalInstaller) func TestLocalInstaller(t *testing.T) { - hh, err := ioutil.TempDir("", "helm-home-") - 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) - } + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) // Make a temp dir tdir, err := ioutil.TempDir("", "helm-installer-") @@ -49,7 +42,7 @@ func TestLocalInstaller(t *testing.T) { } source := "../testdata/plugdir/echo" - i, err := NewForSource(source, "", home) + i, err := NewForSource(source, "") if err != nil { t.Errorf("unexpected error: %s", err) } @@ -58,7 +51,7 @@ func TestLocalInstaller(t *testing.T) { t.Error(err) } - if i.Path() != home.Path("plugins", "echo") { - t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) + if i.Path() != filepath.Join(helmpath.Plugins(), "echo") { + t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) } } diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go index 8ff8d8bdf..b56881210 100644 --- a/pkg/plugin/installer/vcs_installer.go +++ b/pkg/plugin/installer/vcs_installer.go @@ -17,6 +17,7 @@ package installer // import "helm.sh/helm/pkg/plugin/installer" import ( "os" + "path/filepath" "sort" "github.com/Masterminds/semver" @@ -34,25 +35,25 @@ type VCSInstaller struct { base } -func existingVCSRepo(location string, home helmpath.Home) (Installer, error) { +func existingVCSRepo(location string) (Installer, error) { repo, err := vcs.NewRepo("", location) if err != nil { return nil, err } i := &VCSInstaller{ Repo: repo, - base: newBase(repo.Remote(), home), + base: newBase(repo.Remote()), } return i, err } // 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) if err != nil { return nil, err } - cachedpath := home.Path("cache", "plugins", key) + cachedpath := filepath.Join(helmpath.PluginCache(), key) repo, err := vcs.NewRepo(source, cachedpath) if err != nil { return nil, err @@ -60,12 +61,12 @@ func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, i := &VCSInstaller{ Repo: repo, Version: version, - base: newBase(source, home), + base: newBase(source), } 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. func (i *VCSInstaller) Install() error { diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go index c7184c417..47fdecd63 100644 --- a/pkg/plugin/installer/vcs_installer_test.go +++ b/pkg/plugin/installer/vcs_installer_test.go @@ -17,13 +17,13 @@ package installer // import "helm.sh/helm/pkg/plugin/installer" import ( "fmt" - "io/ioutil" "os" "path/filepath" "testing" "github.com/Masterminds/vcs" + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/helmpath" ) @@ -49,15 +49,11 @@ func (r *testRepo) UpdateVersion(version string) error { } func TestVCSInstaller(t *testing.T) { - hh, err := ioutil.TempDir("", "helm-home-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(hh) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - home := helmpath.Home(hh) - if err := os.MkdirAll(home.Plugins(), 0755); err != nil { - t.Fatalf("Could not create %s: %s", home.Plugins(), err) + if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) } source := "https://github.com/adamreese/helm-env" @@ -67,7 +63,7 @@ func TestVCSInstaller(t *testing.T) { 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 { t.Fatalf("unexpected error: %s", err) } @@ -87,8 +83,8 @@ func TestVCSInstaller(t *testing.T) { if repo.current != "0.1.1" { t.Errorf("expected version '0.1.1', got %q", repo.current) } - if i.Path() != home.Path("plugins", "helm-env") { - t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) + if i.Path() != filepath.Join(helmpath.Plugins(), "helm-env") { + t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) } // 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 - 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") } else if err.Error() != "cannot get information about plugin source" { 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) { - hh, err := ioutil.TempDir("", "helm-home-") - 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) - } + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) source := "https://github.com/adamreese/helm-env" version := "0.2.0" - i, err := NewForSource(source, version, home) + i, err := NewForSource(source, version) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -139,21 +127,12 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { } } func TestVCSInstallerUpdate(t *testing.T) { - - hh, err := ioutil.TempDir("", "helm-home-") - 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) - } + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) source := "https://github.com/adamreese/helm-env" - i, err := NewForSource(source, "", home) + i, err := NewForSource(source, "") if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -176,7 +155,7 @@ func TestVCSInstallerUpdate(t *testing.T) { } // Test FindSource method for positive result - pluginInfo, err := FindSource(i.Path(), home) + pluginInfo, err := FindSource(i.Path()) if err != nil { t.Fatal(err) } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 8c8072a27..76f9f461e 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/yaml" helm_env "helm.sh/helm/pkg/cli" + "helm.sh/helm/pkg/helmpath" ) const pluginFileName = "plugin.yaml" @@ -220,17 +221,16 @@ func SetupPluginEnv(settings helm_env.EnvSettings, "HELM_PLUGIN_NAME": shortName, "HELM_PLUGIN_DIR": base, "HELM_BIN": os.Args[0], - - // 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(), + "HELM_PLUGIN": helmpath.Plugins(), // Set vars that convey common information. - "HELM_PATH_REPOSITORY": settings.Home.Repository(), - "HELM_PATH_REPOSITORY_FILE": settings.Home.RepositoryFile(), - "HELM_PATH_CACHE": settings.Home.Cache(), - "HELM_PATH_STARTER": settings.Home.Starters(), + "HELM_PATH_REPOSITORY_FILE": helmpath.RepositoryFile(), + "HELM_PATH_REPOSITORY_CACHE": helmpath.RepositoryCache(), + "HELM_PATH_STARTER": helmpath.Starters(), + "HELM_PATH_CACHE": helmpath.CachePath(), + "HELM_PATH_CONFIG": helmpath.ConfigPath(), + "HELM_PATH_DATA": helmpath.DataPath(), + "HELM_HOME": helmpath.DataPath(), // for backwards compatibility with Helm 2 plugins } { os.Setenv(key, val) } diff --git a/pkg/plugin/testdata/plugdir/hello/hello.sh b/pkg/plugin/testdata/plugdir/hello/hello.sh index db7c0f54d..8778e4431 100755 --- a/pkg/plugin/testdata/plugdir/hello/hello.sh +++ b/pkg/plugin/testdata/plugdir/hello/hello.sh @@ -7,7 +7,7 @@ echo $* echo "ENVIRONMENT" echo $TILLER_HOST -echo $HELM_HOME +echo $HELM_PATH_CONFIG $HELM_BIN --host $TILLER_HOST ls --all diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 81d7f8ec9..183dd4157 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -17,6 +17,8 @@ limitations under the License. package repo // import "helm.sh/helm/pkg/repo" import ( + "crypto/rand" + "encoding/base64" "fmt" "io/ioutil" "net/url" @@ -29,13 +31,13 @@ import ( "helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/getter" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/provenance" ) // Entry represents a collection of parameters for chart repository type Entry struct { Name string `json:"name"` - Cache string `json:"cache"` URL string `json:"url"` Username string `json:"username"` Password string `json:"password"` @@ -112,10 +114,7 @@ func (r *ChartRepository) Load() error { } // DownloadIndexFile fetches the index from a repository. -// -// 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 { +func (r *ChartRepository) DownloadIndexFile() error { var indexURL string parsedURL, err := url.Parse(r.Config.URL) if err != nil { @@ -139,18 +138,7 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { return err } - // In Helm 2.2.0 the config.cache was accidentally switched to an absolute - // 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) + return ioutil.WriteFile(helmpath.CacheIndex(r.Config.Name), index, 0644) } // 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) { // Download and write the index file to a temporary location - tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file") - if err != nil { - return "", errors.Errorf("cannot write index file for repository requested") - } - defer os.Remove(tempIndexFile.Name()) + buf := make([]byte, 20) + rand.Read(buf) + name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-") c := Entry{ URL: repoURL, @@ -216,17 +202,18 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion CertFile: certFile, KeyFile: keyFile, CAFile: caFile, + Name: name, } r, err := NewChartRepository(&c, getters) if err != nil { 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) } // 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 { return "", err } diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 5cbc8833b..d3c18a64b 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -28,11 +28,14 @@ import ( "testing" "time" + "sigs.k8s.io/yaml" + + "helm.sh/helm/internal/test/ensure" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/getter" - - "sigs.k8s.io/yaml" + "helm.sh/helm/pkg/helmpath" + "helm.sh/helm/pkg/helmpath/xdg" ) const ( @@ -129,6 +132,9 @@ func (g *CustomGetter) Get(href string) (*bytes.Buffer, error) { } func TestIndexCustomSchemeDownload(t *testing.T) { + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) + repoName := "gcs-repo" repoURL := "gs://some-gcs-bucket" myCustomGetter := &CustomGetter{} @@ -155,7 +161,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) { } 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) } @@ -276,6 +282,8 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { } func TestFindChartInRepoURL(t *testing.T) { + setupCacheHome(t) + srv, err := startLocalServerForTests(nil) if err != nil { t.Fatal(err) @@ -300,6 +308,8 @@ func TestFindChartInRepoURL(t *testing.T) { } func TestErrorFindChartInRepoURL(t *testing.T) { + setupCacheHome(t) + _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", getter.All(cli.EnvSettings{})) if err == nil { 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) } } + +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) + } +} diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index 329b80b29..408b9773d 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -19,12 +19,12 @@ package repo import ( "io/ioutil" "os" - "path/filepath" "testing" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/getter" + "helm.sh/helm/pkg/helmpath" ) const ( @@ -135,31 +135,25 @@ func TestDownloadIndexFile(t *testing.T) { } defer srv.Close() - dirName, err := ioutil.TempDir("", "tmp") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dirName) + setupCacheHome(t) - indexFilePath := filepath.Join(dirName, testRepo+"-index.yaml") r, err := NewChartRepository(&Entry{ - Name: testRepo, - URL: srv.URL, - Cache: indexFilePath, + Name: testRepo, + URL: srv.URL, }, getter.All(cli.EnvSettings{})) if err != nil { 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) } - 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) } - b, err := ioutil.ReadFile(indexFilePath) + b, err := ioutil.ReadFile(helmpath.CacheIndex(testRepo)) if err != nil { t.Errorf("error reading index file: %#v", err) } diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index aa6f84e3f..54152a54e 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -17,7 +17,6 @@ limitations under the License. package repo // import "helm.sh/helm/pkg/repo" import ( - "fmt" "io/ioutil" "os" "time" @@ -30,7 +29,7 @@ import ( // is fixable. 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 { APIVersion string `json:"apiVersion"` Generated time.Time `json:"generated"` @@ -76,9 +75,8 @@ func LoadFile(path string) (*File, error) { r := NewFile() for k, v := range m { r.Add(&Entry{ - Name: k, - URL: v, - Cache: fmt.Sprintf("%s-index.yaml", k), + Name: k, + URL: v, }) } return r, ErrRepoOutOfDate diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go index acec29bcc..2d6e46bbd 100644 --- a/pkg/repo/repo_test.go +++ b/pkg/repo/repo_test.go @@ -27,14 +27,12 @@ func TestFile(t *testing.T) { rf := NewFile() rf.Add( &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - Cache: "stable-index.yaml", + Name: "stable", + URL: "https://example.com/stable/charts", }, &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - Cache: "incubator-index.yaml", + Name: "incubator", + URL: "https://example.com/incubator", }, ) @@ -56,23 +54,18 @@ func TestFile(t *testing.T) { if stable.URL != "https://example.com/stable/charts" { t.Error("Wrong URL for stable") } - if stable.Cache != "stable-index.yaml" { - t.Error("Wrong cache name for stable") - } } func TestNewFile(t *testing.T) { expects := NewFile() expects.Add( &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - Cache: "stable-index.yaml", + Name: "stable", + URL: "https://example.com/stable/charts", }, &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - Cache: "incubator-index.yaml", + Name: "incubator", + URL: "https://example.com/incubator", }, ) @@ -93,9 +86,6 @@ func TestNewFile(t *testing.T) { if expect.URL != got.URL { t.Errorf("Expected url %q, got %q", expect.URL, got.URL) } - if expect.Cache != got.Cache { - t.Errorf("Expected cache %q, got %q", expect.Cache, got.Cache) - } } } @@ -124,14 +114,12 @@ func TestRemoveRepository(t *testing.T) { sampleRepository := NewFile() sampleRepository.Add( &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - Cache: "stable-index.yaml", + Name: "stable", + URL: "https://example.com/stable/charts", }, &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - Cache: "incubator-index.yaml", + Name: "incubator", + URL: "https://example.com/incubator", }, ) @@ -151,20 +139,17 @@ func TestUpdateRepository(t *testing.T) { sampleRepository := NewFile() sampleRepository.Add( &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - Cache: "stable-index.yaml", + Name: "stable", + URL: "https://example.com/stable/charts", }, &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - Cache: "incubator-index.yaml", + Name: "incubator", + URL: "https://example.com/incubator", }, ) newRepoName := "sample" sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - Cache: "sample-index.yaml", + URL: "https://example.com/sample", }) if !sampleRepository.Has(newRepoName) { @@ -173,8 +158,7 @@ func TestUpdateRepository(t *testing.T) { repoCount := len(sampleRepository.Repositories) sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - Cache: "sample-index.yaml", + URL: "https://example.com/sample", }) if repoCount != len(sampleRepository.Repositories) { @@ -186,14 +170,12 @@ func TestWriteFile(t *testing.T) { sampleRepository := NewFile() sampleRepository.Add( &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - Cache: "stable-index.yaml", + Name: "stable", + URL: "https://example.com/stable/charts", }, &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - Cache: "incubator-index.yaml", + Name: "incubator", + URL: "https://example.com/incubator", }, ) diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index 5b63b9e24..e8dab3b36 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -35,22 +35,21 @@ import ( // // The caller is responsible for destroying the temp directory as well as stopping // the server. -func NewTempServer(glob string) (*Server, helmpath.Home, error) { +func NewTempServer(glob string) (*Server, error) { tdir, err := ioutil.TempDir("", "helm-repotest-") - tdirh := helmpath.Home(tdir) if err != nil { - return nil, tdirh, err + return nil, err } srv := NewServer(tdir) if glob != "" { if _, err := srv.CopyCharts(glob); err != nil { srv.Stop() - return srv, tdirh, err + return srv, err } } - return srv, tdirh, nil + return srv, nil } // NewServer creates a repository server for testing. @@ -71,7 +70,7 @@ func NewServer(docroot string) *Server { } srv.Start() // Add the testing repository as the only repo. - if err := setTestingRepository(helmpath.Home(docroot), "test", srv.URL()); err != nil { + if err := setTestingRepository(srv.URL()); err != nil { panic(err) } return srv @@ -160,25 +159,21 @@ func (s *Server) URL() string { return s.srv.URL } -// LinkIndices links the index created with CreateIndex and makes a symboic link to the repositories/cache directory. +// LinkIndices links the index created with CreateIndex and makes a symbolic link to the cache index. // // This makes it possible to simulate a local cache of a repository. func (s *Server) LinkIndices() error { - destfile := "test-index.yaml" - // Link the index.yaml file to the lstart := filepath.Join(s.docroot, "index.yaml") - ldest := filepath.Join(s.docroot, "repository/cache", destfile) + ldest := helmpath.CacheIndex("test") return os.Symlink(lstart, ldest) } -// setTestingRepository sets up a testing repository.yaml with only the given name/URL. -func setTestingRepository(home helmpath.Home, name, url string) error { +// setTestingRepository sets up a testing repository.yaml with only the given URL. +func setTestingRepository(url string) error { r := repo.NewFile() r.Add(&repo.Entry{ - Name: name, - URL: url, - Cache: home.CacheIndex(name), + Name: "test", + URL: url, }) - os.MkdirAll(filepath.Join(home.Repository(), name), 0755) - return r.WriteFile(home.RepositoryFile(), 0644) + return r.WriteFile(helmpath.RepositoryFile(), 0644) } diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go index 5ecbcbda4..1d11b8ec9 100644 --- a/pkg/repo/repotest/server_test.go +++ b/pkg/repo/repotest/server_test.go @@ -18,25 +18,23 @@ package repotest import ( "io/ioutil" "net/http" - "os" "path/filepath" "testing" "sigs.k8s.io/yaml" + "helm.sh/helm/internal/test/ensure" + "helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/repo" ) // Young'n, in these here parts, we test our tests. func TestServer(t *testing.T) { - docroot, err := ioutil.TempDir("", "helm-repotest-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(docroot) + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) - srv := NewServer(docroot) + srv := NewServer(helmpath.CachePath()) defer srv.Stop() c, err := srv.CopyCharts("testdata/*.tgz") @@ -104,19 +102,17 @@ func TestServer(t *testing.T) { } func TestNewTempServer(t *testing.T) { - srv, tdir, err := NewTempServer("testdata/examplechart-0.1.0.tgz") + ensure.HelmHome(t) + defer ensure.CleanHomeDirs(t) + + srv, err := NewTempServer("testdata/examplechart-0.1.0.tgz") if err != nil { t.Fatal(err) } defer func() { srv.Stop() - os.RemoveAll(tdir.String()) }() - if _, err := os.Stat(tdir.String()); err != nil { - t.Fatal(err) - } - res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz") if err != nil { t.Error(err) diff --git a/pkg/resolver/resolver.go b/pkg/resolver/resolver.go index 866574f81..679c5046e 100644 --- a/pkg/resolver/resolver.go +++ b/pkg/resolver/resolver.go @@ -35,14 +35,12 @@ import ( // Resolver resolves dependencies from semantic version ranges to a particular version. type Resolver struct { chartpath string - helmhome helmpath.Home } // New creates a new resolver for a given chart and a given helm home. -func New(chartpath string, helmhome helmpath.Home) *Resolver { +func New(chartpath string) *Resolver { return &Resolver{ chartpath: chartpath, - helmhome: helmhome, } } @@ -71,7 +69,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) } - repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name])) + repoIndex, err := repo.LoadIndexFile(helmpath.CacheIndex(repoNames[d.Name])) if err != nil { return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") } diff --git a/pkg/resolver/resolver_test.go b/pkg/resolver/resolver_test.go index 3e1b55b12..a31ac311a 100644 --- a/pkg/resolver/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -16,12 +16,15 @@ limitations under the License. package resolver import ( + "os" "testing" "helm.sh/helm/pkg/chart" + "helm.sh/helm/pkg/helmpath/xdg" ) func TestResolve(t *testing.T) { + os.Setenv(xdg.CacheHomeEnvVar, "testdata") tests := []struct { name string req []*chart.Dependency @@ -88,7 +91,7 @@ func TestResolve(t *testing.T) { } repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} - r := New("testdata/chartpath", "testdata/helmhome") + r := New("testdata/chartpath") for _, tt := range tests { l, err := r.Resolve(tt.req, repoNames) if err != nil { diff --git a/pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml b/pkg/resolver/testdata/helm/repository/kubernetes-charts-index.yaml similarity index 100% rename from pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml rename to pkg/resolver/testdata/helm/repository/kubernetes-charts-index.yaml diff --git a/scripts/completions.bash b/scripts/completions.bash index 34eb02875..027cc3322 100644 --- a/scripts/completions.bash +++ b/scripts/completions.bash @@ -235,7 +235,6 @@ _helm_completion() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -262,7 +261,6 @@ _helm_create() two_word_flags+=("-p") local_nonpersistent_flags+=("--starter=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -302,7 +300,6 @@ _helm_delete() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -328,7 +325,6 @@ _helm_dependency_build() flags+=("--verify") local_nonpersistent_flags+=("--verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -350,7 +346,6 @@ _helm_dependency_list() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -378,7 +373,6 @@ _helm_dependency_update() flags+=("--verify") local_nonpersistent_flags+=("--verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -403,7 +397,6 @@ _helm_dependency() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -450,7 +443,6 @@ _helm_fetch() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -474,7 +466,6 @@ _helm_get_hooks() flags+=("--revision=") local_nonpersistent_flags+=("--revision=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -498,7 +489,6 @@ _helm_get_manifest() flags+=("--revision=") local_nonpersistent_flags+=("--revision=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -525,7 +515,6 @@ _helm_get_values() flags+=("--revision=") local_nonpersistent_flags+=("--revision=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -562,7 +551,6 @@ _helm_get() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -596,7 +584,6 @@ _helm_history() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -618,7 +605,6 @@ _helm_home() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -670,7 +656,6 @@ _helm_init() flags+=("--upgrade") local_nonpersistent_flags+=("--upgrade") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -706,7 +691,6 @@ _helm_inspect_chart() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -742,7 +726,6 @@ _helm_inspect_values() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -780,7 +763,6 @@ _helm_inspect() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -852,7 +834,6 @@ _helm_install() flags+=("--wait") local_nonpersistent_flags+=("--wait") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -876,7 +857,6 @@ _helm_lint() flags+=("--strict") local_nonpersistent_flags+=("--strict") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -935,7 +915,6 @@ _helm_list() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -970,7 +949,6 @@ _helm_package() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -994,7 +972,6 @@ _helm_plugin_install() flags+=("--version=") local_nonpersistent_flags+=("--version=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1016,7 +993,6 @@ _helm_plugin_list() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1038,7 +1014,6 @@ _helm_plugin_remove() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1060,7 +1035,6 @@ _helm_plugin_update() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1086,7 +1060,6 @@ _helm_plugin() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1116,7 +1089,6 @@ _helm_repo_add() flags+=("--no-update") local_nonpersistent_flags+=("--no-update") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1142,7 +1114,6 @@ _helm_repo_index() flags+=("--url=") local_nonpersistent_flags+=("--url=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1164,7 +1135,6 @@ _helm_repo_list() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1186,7 +1156,6 @@ _helm_repo_remove() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1208,7 +1177,6 @@ _helm_repo_update() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1235,7 +1203,6 @@ _helm_repo() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1272,7 +1239,6 @@ _helm_reset() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1316,7 +1282,6 @@ _helm_rollback() flags+=("--wait") local_nonpersistent_flags+=("--wait") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1347,7 +1312,6 @@ _helm_search() flags+=("-l") local_nonpersistent_flags+=("--versions") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1375,7 +1339,6 @@ _helm_serve() flags+=("--url=") local_nonpersistent_flags+=("--url=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1408,7 +1371,6 @@ _helm_status() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1444,7 +1406,6 @@ _helm_test() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1520,7 +1481,6 @@ _helm_upgrade() flags+=("--wait") local_nonpersistent_flags+=("--wait") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1544,7 +1504,6 @@ _helm_verify() flags+=("--keyring=") local_nonpersistent_flags+=("--keyring=") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1584,7 +1543,6 @@ _helm_version() flags+=("--tls-verify") local_nonpersistent_flags+=("--tls-verify") flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=") @@ -1631,7 +1589,6 @@ _helm() flags_completion=() flags+=("--debug") - flags+=("--home=") flags+=("--host=") flags+=("--kube-context=") flags+=("--tiller-namespace=")