Merge branch 'dev-v3' of https://github.com/helm/helm into test-as-hook

pull/6054/head
Jacob LeGrone 5 years ago
commit 08b2d8a2dc
No known key found for this signature in database
GPG Key ID: 5FD0852F235368C1

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

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

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

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

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

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

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

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

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

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

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

@ -28,6 +28,7 @@ import (
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/release" "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 { func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "install [NAME] [CHART]", Use: "install [NAME] [CHART]",
@ -104,7 +106,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: installDesc, Long: installDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, out) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return err 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 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.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during 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") 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.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.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") 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) 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.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.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)") 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") 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) debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0") 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) 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 return nil, err
} }
@ -195,7 +198,6 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: cp, ChartPath: cp,
HelmHome: settings.Home,
Keyring: client.ChartPathOptions.Keyring, Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false, SkipUpdate: false,
Getters: getter.All(settings), Getters: getter.All(settings),
@ -210,7 +212,7 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
} }
client.Namespace = getNamespace() client.Namespace = getNamespace()
return client.Run(chartRequested) return client.Run(chartRequested, vals)
} }
// isChartInstallable validates if a chart can be installed // isChartInstallable validates if a chart can be installed

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
) )
var longLintHelp = ` var longLintHelp = `
@ -38,6 +39,7 @@ or recommendation, it will emit [WARNING] messages.
func newLintCmd(out io.Writer) *cobra.Command { func newLintCmd(out io.Writer) *cobra.Command {
client := action.NewLint() client := action.NewLint()
valueOpts := &values.Options{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "lint PATH", Use: "lint PATH",
@ -49,10 +51,11 @@ func newLintCmd(out io.Writer) *cobra.Command {
paths = args paths = args
} }
client.Namespace = getNamespace() client.Namespace = getNamespace()
if err := client.ValueOptions.MergeValues(settings); err != nil { vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err return err
} }
result := client.Run(paths) result := client.Run(paths, vals)
var message strings.Builder var message strings.Builder
fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors)) fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors))
for _, err := range result.Errors { for _, err := range result.Errors {
@ -72,7 +75,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
addValueOptionsFlags(f, &client.ValueOptions) addValueOptionsFlags(f, valueOpts)
return cmd return cmd
} }

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

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter" "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 { func newPackageCmd(out io.Writer) *cobra.Command {
client := action.NewPackage() client := action.NewPackage()
valueOpts := &values.Options{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]", 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") 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 return err
} }
@ -74,7 +77,6 @@ func newPackageCmd(out io.Writer) *cobra.Command {
downloadManager := &downloader.Manager{ downloadManager := &downloader.Manager{
Out: ioutil.Discard, Out: ioutil.Discard,
ChartPath: path, ChartPath: path,
HelmHome: settings.Home,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
Debug: settings.Debug, Debug: settings.Debug,
@ -84,7 +86,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return err return err
} }
} }
p, err := client.Run(path) p, err := client.Run(path, vals)
if err != nil { if err != nil {
return err 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.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.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`) 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 return cmd
} }

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

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

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

@ -25,35 +25,25 @@ import (
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
type pluginListOptions struct {
home helmpath.Home
}
func newPluginListCmd(out io.Writer) *cobra.Command { func newPluginListCmd(out io.Writer) *cobra.Command {
o := &pluginListOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home debug("pluginDirs: %s", helmpath.Plugins())
return o.run(out) plugins, err := findPlugins(helmpath.Plugins())
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 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
}

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

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

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

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

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

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

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

@ -29,39 +29,28 @@ import (
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
type repoListOptions struct {
home helmpath.Home
}
func newRepoListCmd(out io.Writer) *cobra.Command { func newRepoListCmd(out io.Writer) *cobra.Command {
o := &repoListOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "list chart repositories", Short: "list chart repositories",
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home f, err := repo.LoadFile(helmpath.RepositoryFile())
return o.run(out) 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 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
}

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

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

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

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

@ -19,6 +19,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strconv"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -31,32 +32,39 @@ const rollbackDesc = `
This command rolls back a release to a previous revision. 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 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 second is a revision (version) number. If this argument is omitted, it will
'helm history RELEASE'. roll back to the previous release.
To see revision numbers, run 'helm history RELEASE'.
` `
func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewRollback(cfg) client := action.NewRollback(cfg)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rollback [RELEASE] [REVISION]", Use: "rollback <RELEASE> [REVISION]",
Short: "roll back a release to a previous revision", Short: "roll back a release to a previous revision",
Long: rollbackDesc, Long: rollbackDesc,
Args: require.ExactArgs(2), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
_, err := client.Run(args[0]) if len(args) > 1 {
if err != nil { 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 return err
} }
fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n") fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n")
return nil return nil
}, },
} }
f := cmd.Flags() 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.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.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") f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed")

@ -55,8 +55,13 @@ func TestRollbackCmd(t *testing.T) {
golden: "output/rollback-wait.txt", golden: "output/rollback-wait.txt",
rels: rels, rels: rels,
}, { }, {
name: "rollback a release without revision", name: "rollback a release without revision",
cmd: "rollback funny-honey", 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", golden: "output/rollback-no-args.txt",
rels: rels, rels: rels,
wantError: true, wantError: true,

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

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

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

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

@ -25,6 +25,7 @@ import (
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
) )
const templateDesc = ` const templateDesc = `
@ -39,6 +40,7 @@ is done.
func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var validate bool var validate bool
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "template [NAME] [CHART]", Use: "template [NAME] [CHART]",
@ -50,7 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.ReleaseName = "RELEASE-NAME" client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
client.ClientOnly = !validate client.ClientOnly = !validate
rel, err := runInstall(args, client, out) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return err return err
} }
@ -60,7 +62,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() 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.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") f.BoolVar(&validate, "validate", false, "establish a connection to Kubernetes for schema validation")

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

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

@ -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 <RELEASE> [REVISION] [flags]

@ -0,0 +1 @@
Rollback was a success! Happy Helming!

@ -27,6 +27,7 @@ import (
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/storage/driver" "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 { func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewUpgrade(cfg) client := action.NewUpgrade(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]", Use: "upgrade [RELEASE] [CHART]",
@ -71,7 +73,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
if err := client.ValueOptions.MergeValues(settings); err != nil { vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err 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]) fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
instClient := action.NewInstall(cfg) instClient := action.NewInstall(cfg)
instClient.ChartPathOptions = client.ChartPathOptions instClient.ChartPathOptions = client.ChartPathOptions
instClient.ValueOptions = client.ValueOptions
instClient.DryRun = client.DryRun instClient.DryRun = client.DryRun
instClient.DisableHooks = client.DisableHooks instClient.DisableHooks = client.DisableHooks
instClient.Timeout = client.Timeout instClient.Timeout = client.Timeout
@ -98,7 +100,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Namespace = client.Namespace instClient.Namespace = client.Namespace
instClient.Atomic = client.Atomic instClient.Atomic = client.Atomic
_, err := runInstall(args, instClient, out) _, err := runInstall(args, instClient, valueOpts, out)
return err 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 { if err != nil {
return errors.Wrap(err, "UPGRADE FAILED") 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.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.") 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) addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, &client.ValueOptions) addValueOptionsFlags(f, valueOpts)
return cmd return cmd
} }

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

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

@ -20,7 +20,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -30,7 +29,6 @@ import (
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
@ -38,13 +36,13 @@ import (
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
kubefake "helm.sh/helm/pkg/kube/fake" kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/releaseutil"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage" "helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver" "helm.sh/helm/pkg/storage/driver"
"helm.sh/helm/pkg/strvals"
"helm.sh/helm/pkg/version" "helm.sh/helm/pkg/version"
) )
@ -68,7 +66,6 @@ type Install struct {
cfg *Configuration cfg *Configuration
ChartPathOptions ChartPathOptions
ValueOptions
ClientOnly bool ClientOnly bool
DryRun bool DryRun bool
@ -86,13 +83,6 @@ type Install struct {
Atomic bool Atomic bool
} }
type ValueOptions struct {
ValueFiles []string
StringValues []string
Values []string
rawValues map[string]interface{}
}
type ChartPathOptions struct { type ChartPathOptions struct {
CaFile string // --ca-file CaFile string // --ca-file
CertFile string // --cert-file CertFile string // --cert-file
@ -115,7 +105,7 @@ func NewInstall(cfg *Configuration) *Install {
// Run executes the installation // Run executes the installation
// //
// If DryRun is set to true, this will prepare the release, but not install it // 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 { if err := i.availableName(); err != nil {
return nil, err return nil, err
} }
@ -128,7 +118,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
i.cfg.Releases = storage.Init(driver.NewMemory()) 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 return nil, err
} }
@ -146,12 +136,12 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
Namespace: i.Namespace, Namespace: i.Namespace,
IsInstall: true, IsInstall: true,
} }
valuesToRender, err := chartutil.ToRenderValues(chrt, i.rawValues, options, caps) valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rel := i.createRelease(chrt, i.rawValues) rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir) rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir)
// Even for errors, attach this if available // Even for errors, attach this if available
@ -538,7 +528,6 @@ OUTER:
// Order of resolution: // Order of resolution:
// - relative to current working directory // - relative to current working directory
// - if path is absolute or begins with '.', error out here // - if path is absolute or begins with '.', error out here
// - chart repos in $HELM_HOME
// - URL // - URL
// //
// If 'verify' is true, this will attempt to also verify the chart. // If 'verify' is true, this will attempt to also verify the chart.
@ -562,16 +551,10 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
return name, errors.Errorf("path %q not found", name) return name, errors.Errorf("path %q not found", name)
} }
crepo := filepath.Join(settings.Home.Repository(), name)
if _, err := os.Stat(crepo); err == nil {
return filepath.Abs(crepo)
}
dl := downloader.ChartDownloader{ dl := downloader.ChartDownloader{
HelmHome: settings.Home, Out: os.Stdout,
Out: os.Stdout, Keyring: c.Keyring,
Keyring: c.Keyring, Getters: getter.All(settings),
Getters: getter.All(settings),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(c.Username, c.Password), getter.WithBasicAuth(c.Username, c.Password),
}, },
@ -588,11 +571,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
name = chartURL name = chartURL
} }
if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { if _, err := os.Stat(helmpath.Archive()); os.IsNotExist(err) {
os.MkdirAll(settings.Home.Archive(), 0744) os.MkdirAll(helmpath.Archive(), 0744)
} }
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) filename, _, err := dl.DownloadTo(name, version, helmpath.Archive())
if err == nil { if err == nil {
lname, err := filepath.Abs(filename) lname, err := filepath.Abs(filename)
if err != nil { if err != nil {
@ -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) 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, &currentMap); 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
}

@ -22,7 +22,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"regexp" "regexp"
"testing" "testing"
@ -53,8 +52,8 @@ func installAction(t *testing.T) *Install {
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart()) res, err := instAction.Run(buildChart(), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -79,7 +78,7 @@ func TestInstallReleaseClientOnly(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.ClientOnly = true 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.Capabilities, chartutil.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) 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) { func TestInstallRelease_NoName(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "" instAction.ReleaseName = ""
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
_, err := instAction.Run(buildChart()) _, err := instAction.Run(buildChart(), vals)
if err == nil { if err == nil {
t.Fatal("expected failure when no name is specified") t.Fatal("expected failure when no name is specified")
} }
@ -100,8 +99,8 @@ func TestInstallRelease_WithNotes(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("note here"))) res, err := instAction.Run(buildChart(withNotes("note here")), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -127,8 +126,8 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}"))) res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -146,8 +145,8 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child")))) res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -163,8 +162,8 @@ func TestInstallRelease_DryRun(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRun = true instAction.DryRun = true
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates())) res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -189,8 +188,8 @@ func TestInstallRelease_NoHooks(t *testing.T) {
instAction.ReleaseName = "no-hooks" instAction.ReleaseName = "no-hooks"
instAction.cfg.Releases.Create(releaseStub()) instAction.cfg.Releases.Create(releaseStub())
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart()) res, err := instAction.Run(buildChart(), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -206,8 +205,8 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
failer.WatchUntilReadyError = fmt.Errorf("Failed watch") failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart()) res, err := instAction.Run(buildChart(), vals)
is.Error(err) is.Error(err)
is.Contains(res.Info.Description, "failed post-install") is.Contains(res.Info.Description, "failed post-install")
is.Equal(release.StatusFailed, res.Info.Status) is.Equal(release.StatusFailed, res.Info.Status)
@ -223,8 +222,8 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
instAction.cfg.Releases.Create(rel) instAction.cfg.Releases.Create(rel)
instAction.ReleaseName = rel.Name instAction.ReleaseName = rel.Name
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart()) res, err := instAction.Run(buildChart(), vals)
is.NoError(err) is.NoError(err)
// This should have been auto-incremented // This should have been auto-incremented
@ -239,14 +238,14 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
func TestInstallRelease_KubeVersion(t *testing.T) { func TestInstallRelease_KubeVersion(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withKube(">=0.0.0"))) _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
is.NoError(err) is.NoError(err)
// This should fail for a few hundred years // This should fail for a few hundred years
instAction.ReleaseName = "should-fail" instAction.ReleaseName = "should-fail"
instAction.rawValues = map[string]interface{}{} vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0"))) _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err) is.Error(err)
is.Contains(err.Error(), "chart requires kubernetesVersion") is.Contains(err.Error(), "chart requires kubernetesVersion")
} }
@ -259,9 +258,9 @@ func TestInstallRelease_Wait(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Wait = true 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.Error(err)
is.Contains(res.Info.Description, "I timed out") is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
@ -277,9 +276,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Atomic = true 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.Error(err)
is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "atomic") is.Contains(err.Error(), "atomic")
@ -298,9 +297,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.DeleteError = fmt.Errorf("uninstall fail") failer.DeleteError = fmt.Errorf("uninstall fail")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Atomic = true 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.Error(err)
is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "uninstall fail") 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) { func TestInstallReleaseOutputDir(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.rawValues = map[string]interface{}{} vals := map[string]interface{}{}
dir, err := ioutil.TempDir("", "output-dir") dir, err := ioutil.TempDir("", "output-dir")
if err != nil { if err != nil {
@ -445,7 +389,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
instAction.OutputDir = dir instAction.OutputDir = dir
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate())) _, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }

@ -35,8 +35,6 @@ var errLintNoChart = errors.New("no chart found for linting (missing Chart.yaml)
// //
// It provides the implementation of 'helm lint'. // It provides the implementation of 'helm lint'.
type Lint struct { type Lint struct {
ValueOptions
Strict bool Strict bool
Namespace string Namespace string
} }
@ -53,7 +51,7 @@ func NewLint() *Lint {
} }
// Run executes 'helm Lint' against the given chart. // 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 lowestTolerance := support.ErrorSev
if l.Strict { if l.Strict {
lowestTolerance = support.WarningSev lowestTolerance = support.WarningSev
@ -61,7 +59,7 @@ func (l *Lint) Run(paths []string) *LintResult {
result := &LintResult{} result := &LintResult{}
for _, path := range paths { 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 { if err == errLintNoChart {
result.Errors = append(result.Errors, err) result.Errors = append(result.Errors, err)
} }

@ -36,8 +36,6 @@ import (
// //
// It provides the implementation of 'helm package'. // It provides the implementation of 'helm package'.
type Package struct { type Package struct {
ValueOptions
Sign bool Sign bool
Key string Key string
Keyring 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. // 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) ch, err := loader.LoadDir(path)
if err != nil { if err != nil {
return "", err return "", err
} }
combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues) combinedVals, err := chartutil.CoalesceValues(ch, vals)
if err != nil { if err != nil {
return "", err return "", err
} }

@ -57,11 +57,10 @@ func (p *Pull) Run(chartRef string) (string, error) {
var out strings.Builder var out strings.Builder
c := downloader.ChartDownloader{ c := downloader.ChartDownloader{
HelmHome: p.Settings.Home, Out: &out,
Out: &out, Keyring: p.Keyring,
Keyring: p.Keyring, Verify: downloader.VerifyNever,
Verify: downloader.VerifyNever, Getters: getter.All(p.Settings),
Getters: getter.All(p.Settings),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(p.Username, p.Password), getter.WithBasicAuth(p.Username, p.Password),
}, },

@ -39,7 +39,6 @@ type Upgrade struct {
cfg *Configuration cfg *Configuration
ChartPathOptions ChartPathOptions
ValueOptions
Install bool Install bool
Devel bool Devel bool
@ -66,8 +65,8 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
} }
// Run executes the upgrade on the given release. // Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) { func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := chartutil.ProcessDependencies(chart, u.rawValues); err != nil { if err := chartutil.ProcessDependencies(chart, vals); err != nil {
return nil, err 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) return nil, errors.Errorf("release name is invalid: %s", name)
} }
u.cfg.Log("preparing upgrade for %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 { if err != nil {
return nil, err return nil, err
} }
@ -122,7 +121,7 @@ func validateReleaseName(releaseName string) error {
} }
// prepareUpgrade builds an upgraded release for an upgrade operation. // 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 { if chart == nil {
return nil, nil, errMissingChart 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 // 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 return nil, nil, err
} }
@ -158,7 +158,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
valuesToRender, err := chartutil.ToRenderValues(chart, u.rawValues, options, caps) valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -173,7 +173,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
Name: name, Name: name,
Namespace: currentRelease.Namespace, Namespace: currentRelease.Namespace,
Chart: chart, Chart: chart,
Config: u.rawValues, Config: vals,
Info: &release.Info{ Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed, FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: Timestamper(), 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 // This is skipped if the u.ResetValues flag is set, in which case the
// request values are not altered. // 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 u.ResetValues {
// If ResetValues is set, we completely ignore current.Config. // If ResetValues is set, we completely ignore current.Config.
u.cfg.Log("resetting values to the chart's original version") 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. // 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: // We have to regenerate the old coalesced values:
oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
if err != nil { 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 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.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 { func validateManifest(c kube.Interface, manifest []byte) error {

@ -49,9 +49,9 @@ func TestUpgradeRelease_Wait(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Wait = true 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) req.Error(err)
is.Contains(res.Info.Description, "I timed out") is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
@ -74,9 +74,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
failer.WatchUntilReadyError = fmt.Errorf("arming key removed") failer.WatchUntilReadyError = fmt.Errorf("arming key removed")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Atomic = true 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) req.Error(err)
is.Contains(err.Error(), "arming key removed") is.Contains(err.Error(), "arming key removed")
is.Contains(err.Error(), "atomic") is.Contains(err.Error(), "atomic")
@ -99,9 +99,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
failer.UpdateError = fmt.Errorf("update fail") failer.UpdateError = fmt.Errorf("update fail")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Atomic = true 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) req.Error(err)
is.Contains(err.Error(), "update fail") is.Contains(err.Error(), "update fail")
is.Contains(err.Error(), "an error occurred while rolling back the release") 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 upAction.ReuseValues = true
// setting newValues and upgrading // setting newValues and upgrading
upAction.rawValues = newValues res, err := upAction.Run(rel.Name, buildChart(), newValues)
res, err := upAction.Run(rel.Name, buildChart())
is.NoError(err) is.NoError(err)
// Now make sure it is actually upgraded // Now make sure it is actually upgraded

@ -18,7 +18,7 @@ package chart
// APIVersionV1 is the API version number for version 1. // APIVersionV1 is the API version number for version 1.
const APIVersionV1 = "v1" const APIVersionV1 = "v1"
// APIVersionV1 is the API version number for version 2. // APIVersionV2 is the API version number for version 2.
const APIVersionV2 = "v2" const APIVersionV2 = "v2"
// Chart is a helm package that contains metadata, a default config, zero or more // Chart is a helm package that contains metadata, a default config, zero or more

@ -18,6 +18,7 @@ package loader
import ( import (
"bytes" "bytes"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -103,6 +104,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
// Deprecated: requirements.yaml is deprecated use Chart.yaml. // Deprecated: requirements.yaml is deprecated use Chart.yaml.
// We will handle it for you because we are nice people // We will handle it for you because we are nice people
case f.Name == "requirements.yaml": 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 { if c.Metadata == nil {
c.Metadata = new(chart.Metadata) c.Metadata = new(chart.Metadata)
} }

@ -147,6 +147,19 @@ func TestLoadFileBackslash(t *testing.T) {
verifyDependencies(t, c) 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) { func verifyChart(t *testing.T, c *chart.Chart) {
t.Helper() t.Helper()
if c.Name() == "" { if c.Name() == "" {

@ -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

@ -0,0 +1 @@
This is an install document. The client may display this.

@ -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.

@ -0,0 +1 @@
This should be ignored by the loader, but may be included in a chart.

@ -0,0 +1,5 @@
apiVersion: v1
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0
home: https://helm.sh/helm

@ -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`.

@ -0,0 +1,5 @@
apiVersion: v1
name: mast1
description: A Helm chart for Kubernetes
version: 0.1.0
home: ""

@ -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"

@ -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"]

@ -0,0 +1,2 @@
# The pod name
name: "my-alpine"

@ -0,0 +1 @@
This is a placeholder for documentation.

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0" width="256" height="256" id="test">
<desc>Example icon</desc>
<rect id="first" x="2" y="2" width="40" height="60" fill="navy"/>
<rect id="second" x="15" y="4" width="40" height="60" fill="red"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

@ -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

@ -0,0 +1 @@
Hello {{.Name | default "world"}}

@ -0,0 +1,6 @@
# A values file contains configuration.
name: "Some Name"
section:
name: "Name in a section"

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

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

@ -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, &currentMap); 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
}

@ -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)
}
}

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

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

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

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

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

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

Loading…
Cancel
Save