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
@echo
@echo "==> Running unit tests <=="
HELM_HOME=/no_such_dir go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
.PHONY: test-coverage
test-coverage: vendor
@echo
@echo "==> Running unit tests with coverage <=="
@ HELM_HOME=/no_such_dir ./scripts/coverage.sh
@ ./scripts/coverage.sh
.PHONY: test-style
test-style: vendor $(GOLANGCI_LINT)

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

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

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

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

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

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

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

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

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

@ -28,6 +28,7 @@ import (
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/release"
@ -97,6 +98,7 @@ charts in a repository, use 'helm search'.
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewInstall(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "install [NAME] [CHART]",
@ -104,7 +106,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: installDesc,
Args: require.MinimumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, out)
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return err
}
@ -113,12 +115,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
addInstallFlags(cmd.Flags(), client)
addInstallFlags(cmd.Flags(), client, valueOpts)
return cmd
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
@ -129,11 +131,11 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used")
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
}
func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) {
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
@ -151,7 +153,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
func runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) {
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
@ -171,7 +173,8 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
debug("CHART PATH: %s\n", cp)
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return nil, err
}
@ -195,7 +198,6 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
man := &downloader.Manager{
Out: out,
ChartPath: cp,
HelmHome: settings.Home,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: getter.All(settings),
@ -210,7 +212,7 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
}
client.Namespace = getNamespace()
return client.Run(chartRequested)
return client.Run(chartRequested, vals)
}
// isChartInstallable validates if a chart can be installed

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

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

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
@ -43,6 +44,7 @@ Versioned chart archives are used by Helm package repositories.
func newPackageCmd(out io.Writer) *cobra.Command {
client := action.NewPackage()
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]",
@ -60,7 +62,8 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return errors.New("--keyring is required for signing a package")
}
}
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err
}
@ -74,7 +77,6 @@ func newPackageCmd(out io.Writer) *cobra.Command {
downloadManager := &downloader.Manager{
Out: ioutil.Discard,
ChartPath: path,
HelmHome: settings.Home,
Keyring: client.Keyring,
Getters: getter.All(settings),
Debug: settings.Debug,
@ -84,7 +86,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return err
}
}
p, err := client.Run(path)
p, err := client.Run(path, vals)
if err != nil {
return err
}
@ -102,7 +104,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
return cmd
}

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

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

@ -25,35 +25,25 @@ import (
"helm.sh/helm/pkg/helmpath"
)
type pluginListOptions struct {
home helmpath.Home
}
func newPluginListCmd(out io.Writer) *cobra.Command {
o := &pluginListOptions{}
cmd := &cobra.Command{
Use: "list",
Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out)
debug("pluginDirs: %s", helmpath.Plugins())
plugins, err := findPlugins(helmpath.Plugins())
if err != nil {
return err
}
table := uitable.New()
table.AddRow("NAME", "VERSION", "DESCRIPTION")
for _, p := range plugins {
table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description)
}
fmt.Fprintln(out, table)
return nil
},
}
return cmd
}
func (o *pluginListOptions) run(out io.Writer) error {
debug("pluginDirs: %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
if err != nil {
return err
}
table := uitable.New()
table.AddRow("NAME", "VERSION", "DESCRIPTION")
for _, p := range plugins {
table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description)
}
fmt.Fprintln(out, table)
return nil
}

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

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

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

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

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

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

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

@ -29,39 +29,28 @@ import (
"helm.sh/helm/pkg/repo"
)
type repoListOptions struct {
home helmpath.Home
}
func newRepoListCmd(out io.Writer) *cobra.Command {
o := &repoListOptions{}
cmd := &cobra.Command{
Use: "list",
Short: "list chart repositories",
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out)
f, err := repo.LoadFile(helmpath.RepositoryFile())
if err != nil {
return err
}
if len(f.Repositories) == 0 {
return errors.New("no repositories to show")
}
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range f.Repositories {
table.AddRow(re.Name, re.URL)
}
fmt.Fprintln(out, table)
return nil
},
}
return cmd
}
func (o *repoListOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.home.RepositoryFile())
if err != nil {
return err
}
if len(f.Repositories) == 0 {
return errors.New("no repositories to show")
}
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range f.Repositories {
table.AddRow(re.Name, re.URL)
}
fmt.Fprintln(out, table)
return nil
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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/pkg/action"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/storage/driver"
)
@ -57,6 +58,7 @@ set for a key called 'foo', the 'newbar' value would take precedence:
func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewUpgrade(cfg)
valueOpts := &values.Options{}
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
@ -71,7 +73,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0"
}
if err := client.ValueOptions.MergeValues(settings); err != nil {
vals, err := valueOpts.MergeValues(settings)
if err != nil {
return err
}
@ -89,7 +92,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
instClient := action.NewInstall(cfg)
instClient.ChartPathOptions = client.ChartPathOptions
instClient.ValueOptions = client.ValueOptions
instClient.DryRun = client.DryRun
instClient.DisableHooks = client.DisableHooks
instClient.Timeout = client.Timeout
@ -98,7 +100,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Namespace = client.Namespace
instClient.Atomic = client.Atomic
_, err := runInstall(args, instClient, out)
_, err := runInstall(args, instClient, valueOpts, out)
return err
}
}
@ -114,7 +116,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
}
resp, err := client.Run(args[0], ch)
resp, err := client.Run(args[0], ch, vals)
if err != nil {
return errors.Wrap(err, "UPGRADE FAILED")
}
@ -151,7 +153,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used")
f.IntVar(&client.MaxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, &client.ValueOptions)
addValueOptionsFlags(f, valueOpts)
return cmd
}

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

@ -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"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
@ -30,7 +29,6 @@ import (
"github.com/Masterminds/sprig"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil"
@ -38,13 +36,13 @@ import (
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver"
"helm.sh/helm/pkg/strvals"
"helm.sh/helm/pkg/version"
)
@ -68,7 +66,6 @@ type Install struct {
cfg *Configuration
ChartPathOptions
ValueOptions
ClientOnly bool
DryRun bool
@ -86,13 +83,6 @@ type Install struct {
Atomic bool
}
type ValueOptions struct {
ValueFiles []string
StringValues []string
Values []string
rawValues map[string]interface{}
}
type ChartPathOptions struct {
CaFile string // --ca-file
CertFile string // --cert-file
@ -115,7 +105,7 @@ func NewInstall(cfg *Configuration) *Install {
// Run executes the installation
//
// If DryRun is set to true, this will prepare the release, but not install it
func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := i.availableName(); err != nil {
return nil, err
}
@ -128,7 +118,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
i.cfg.Releases = storage.Init(driver.NewMemory())
}
if err := chartutil.ProcessDependencies(chrt, i.rawValues); err != nil {
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
@ -146,12 +136,12 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
Namespace: i.Namespace,
IsInstall: true,
}
valuesToRender, err := chartutil.ToRenderValues(chrt, i.rawValues, options, caps)
valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps)
if err != nil {
return nil, err
}
rel := i.createRelease(chrt, i.rawValues)
rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir)
// Even for errors, attach this if available
@ -538,7 +528,6 @@ OUTER:
// Order of resolution:
// - relative to current working directory
// - if path is absolute or begins with '.', error out here
// - chart repos in $HELM_HOME
// - URL
//
// If 'verify' is true, this will attempt to also verify the chart.
@ -562,16 +551,10 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
return name, errors.Errorf("path %q not found", name)
}
crepo := filepath.Join(settings.Home.Repository(), name)
if _, err := os.Stat(crepo); err == nil {
return filepath.Abs(crepo)
}
dl := downloader.ChartDownloader{
HelmHome: settings.Home,
Out: os.Stdout,
Keyring: c.Keyring,
Getters: getter.All(settings),
Out: os.Stdout,
Keyring: c.Keyring,
Getters: getter.All(settings),
Options: []getter.Option{
getter.WithBasicAuth(c.Username, c.Password),
},
@ -588,11 +571,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
name = chartURL
}
if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) {
os.MkdirAll(settings.Home.Archive(), 0744)
if _, err := os.Stat(helmpath.Archive()); os.IsNotExist(err) {
os.MkdirAll(helmpath.Archive(), 0744)
}
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive())
filename, _, err := dl.DownloadTo(name, version, helmpath.Archive())
if err == nil {
lname, err := filepath.Abs(filename)
if err != nil {
@ -605,114 +588,3 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
}
// MergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML
func (v *ValueOptions) MergeValues(settings cli.EnvSettings) error {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range v.ValueFiles {
currentMap := map[string]interface{}{}
bytes, err := readFile(filePath, settings)
if err != nil {
return err
}
if err := yaml.Unmarshal(bytes, &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"
"os"
"path/filepath"
"reflect"
"regexp"
"testing"
@ -53,8 +52,8 @@ func installAction(t *testing.T) *Install {
func TestInstallRelease(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -79,7 +78,7 @@ func TestInstallReleaseClientOnly(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ClientOnly = true
instAction.Run(buildChart()) // disregard output
instAction.Run(buildChart(), nil) // disregard output
is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard})
@ -88,8 +87,8 @@ func TestInstallReleaseClientOnly(t *testing.T) {
func TestInstallRelease_NoName(t *testing.T) {
instAction := installAction(t)
instAction.ReleaseName = ""
instAction.rawValues = map[string]interface{}{}
_, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(), vals)
if err == nil {
t.Fatal("expected failure when no name is specified")
}
@ -100,8 +99,8 @@ func TestInstallRelease_WithNotes(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("note here")))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("note here")), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -127,8 +126,8 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -146,8 +145,8 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "with-notes"
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -163,8 +162,8 @@ func TestInstallRelease_DryRun(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.DryRun = true
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates()))
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -189,8 +188,8 @@ func TestInstallRelease_NoHooks(t *testing.T) {
instAction.ReleaseName = "no-hooks"
instAction.cfg.Releases.Create(releaseStub())
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
@ -206,8 +205,8 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
instAction.cfg.KubeClient = failer
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "failed post-install")
is.Equal(release.StatusFailed, res.Info.Status)
@ -223,8 +222,8 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
instAction.cfg.Releases.Create(rel)
instAction.ReleaseName = rel.Name
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
is.NoError(err)
// This should have been auto-incremented
@ -239,14 +238,14 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
func TestInstallRelease_KubeVersion(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
_, err := instAction.Run(buildChart(withKube(">=0.0.0")))
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
is.NoError(err)
// This should fail for a few hundred years
instAction.ReleaseName = "should-fail"
instAction.rawValues = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")))
vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err)
is.Contains(err.Error(), "chart requires kubernetesVersion")
}
@ -259,9 +258,9 @@ func TestInstallRelease_Wait(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Wait = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart())
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
@ -277,9 +276,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart())
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "atomic")
@ -298,9 +297,9 @@ func TestInstallRelease_Atomic(t *testing.T) {
failer.DeleteError = fmt.Errorf("uninstall fail")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
_, err := instAction.Run(buildChart())
_, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "uninstall fail")
@ -377,65 +376,10 @@ func TestNameTemplate(t *testing.T) {
}
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := mergeValues(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeValues(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeValues(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}
func TestInstallReleaseOutputDir(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.rawValues = map[string]interface{}{}
vals := map[string]interface{}{}
dir, err := ioutil.TempDir("", "output-dir")
if err != nil {
@ -445,7 +389,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
instAction.OutputDir = dir
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()))
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}

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

@ -36,8 +36,6 @@ import (
//
// It provides the implementation of 'helm package'.
type Package struct {
ValueOptions
Sign bool
Key string
Keyring string
@ -53,13 +51,13 @@ func NewPackage() *Package {
}
// Run executes 'helm package' against the given chart and returns the path to the packaged chart.
func (p *Package) Run(path string) (string, error) {
func (p *Package) Run(path string, vals map[string]interface{}) (string, error) {
ch, err := loader.LoadDir(path)
if err != nil {
return "", err
}
combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues)
combinedVals, err := chartutil.CoalesceValues(ch, vals)
if err != nil {
return "", err
}

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

@ -39,7 +39,6 @@ type Upgrade struct {
cfg *Configuration
ChartPathOptions
ValueOptions
Install bool
Devel bool
@ -66,8 +65,8 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
}
// Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) {
if err := chartutil.ProcessDependencies(chart, u.rawValues); err != nil {
func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := chartutil.ProcessDependencies(chart, vals); err != nil {
return nil, err
}
@ -79,7 +78,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error)
return nil, errors.Errorf("release name is invalid: %s", name)
}
u.cfg.Log("preparing upgrade for %s", name)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
if err != nil {
return nil, err
}
@ -122,7 +121,7 @@ func validateReleaseName(releaseName string) error {
}
// prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) {
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
if chart == nil {
return nil, nil, errMissingChart
}
@ -134,7 +133,8 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
}
// determine if values will be reused
if err := u.reuseValues(chart, currentRelease); err != nil {
vals, err = u.reuseValues(chart, currentRelease, vals)
if err != nil {
return nil, nil, err
}
@ -158,7 +158,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
if err != nil {
return nil, nil, err
}
valuesToRender, err := chartutil.ToRenderValues(chart, u.rawValues, options, caps)
valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps)
if err != nil {
return nil, nil, err
}
@ -173,7 +173,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele
Name: name,
Namespace: currentRelease.Namespace,
Chart: chart,
Config: u.rawValues,
Config: vals,
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: Timestamper(),
@ -310,11 +310,11 @@ func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release
//
// This is skipped if the u.ResetValues flag is set, in which case the
// request values are not altered.
func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error {
func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
if u.ResetValues {
// If ResetValues is set, we completely ignore current.Config.
u.cfg.Log("resetting values to the chart's original version")
return nil
return newVals, nil
}
// If the ReuseValues flag is set, we always copy the old values over the new config's values.
@ -324,21 +324,21 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) erro
// We have to regenerate the old coalesced values:
oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
if err != nil {
return errors.Wrap(err, "failed to rebuild old values")
return nil, errors.Wrap(err, "failed to rebuild old values")
}
u.rawValues = chartutil.CoalesceTables(u.rawValues, current.Config)
newVals = chartutil.CoalesceTables(newVals, current.Config)
chart.Values = oldVals
return nil
return newVals, nil
}
if len(u.rawValues) == 0 && len(current.Config) > 0 {
if len(newVals) == 0 && len(current.Config) > 0 {
u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version)
u.rawValues = current.Config
newVals = current.Config
}
return nil
return newVals, nil
}
func validateManifest(c kube.Interface, manifest []byte) error {

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

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

@ -18,6 +18,7 @@ package loader
import (
"bytes"
"log"
"os"
"path/filepath"
"strings"
@ -103,6 +104,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
// Deprecated: requirements.yaml is deprecated use Chart.yaml.
// We will handle it for you because we are nice people
case f.Name == "requirements.yaml":
if c.Metadata.APIVersion != chart.APIVersionV1 {
log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.")
}
if c.Metadata == nil {
c.Metadata = new(chart.Metadata)
}

@ -147,6 +147,19 @@ func TestLoadFileBackslash(t *testing.T) {
verifyDependencies(t, c)
}
func TestLoadV2WithReqs(t *testing.T) {
l, err := Loader("testdata/frobnitz.v2.reqs")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load()
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
verifyDependencies(t, c)
verifyDependenciesLock(t, c)
}
func verifyChart(t *testing.T, c *chart.Chart) {
t.Helper()
if c.Name() == "" {

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

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

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

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

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

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

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

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

Loading…
Cancel
Save