Merge branch 'dev-v3' into feat-v3/runAcceptanceTests

pull/6256/head
Marc Khouzam 6 years ago
commit b1419b2427

34
Gopkg.lock generated

@ -212,38 +212,34 @@
version = "v0.1.0" version = "v0.1.0"
[[projects]] [[projects]]
digest = "1:b5f139796b532342966b835fb26fe41b6b488e94b914f1af1aba4cd3a9fee6dc" digest = "1:9d535dbb6316d0227c1335649d12161b0832f6fa60d5754c559a9ba82859fc1e"
name = "github.com/containerd/containerd" name = "github.com/containerd/containerd"
packages = [ packages = [
"archive/compression",
"content", "content",
"content/local", "content/local",
"errdefs", "errdefs",
"filters", "filters",
"images", "images",
"labels",
"log", "log",
"platforms", "platforms",
"reference", "reference",
"remotes", "remotes",
"remotes/docker", "remotes/docker",
"remotes/docker/schema1",
"sys", "sys",
"version",
] ]
pruneopts = "T" pruneopts = "T"
revision = "894b81a4b802e4eb2a91d1ce216b8817763c29fb" revision = "640860a042b93c26c0a33081ee02230def486f81"
version = "v1.2.6" version = "v1.3.0-beta.2"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:1271f7f8cc5f5b2eb0c683f92c7adf8fca1813b9da5218d6df1c9cf4bdc3f8d5" digest = "1:1271f7f8cc5f5b2eb0c683f92c7adf8fca1813b9da5218d6df1c9cf4bdc3f8d5"
name = "github.com/containerd/continuity" name = "github.com/containerd/continuity"
packages = [ packages = ["pathdriver"]
".",
"devices",
"driver",
"pathdriver",
"proto",
"syscallx",
"sysx",
]
pruneopts = "T" pruneopts = "T"
revision = "004b46473808b3e7a4a3049c20e4376c91eb966d" revision = "004b46473808b3e7a4a3049c20e4376c91eb966d"
@ -264,7 +260,7 @@
version = "v1.1.1" version = "v1.1.1"
[[projects]] [[projects]]
digest = "1:543d5301a51341bbdaba9ddf14ae52ffcc8302fc5e79b01f1954c31f02a4aac5" digest = "1:8b5f943c7eec36fe9341e1bd9de2b2c0f0bee16374fe461ed0837b4dcdcea711"
name = "github.com/deislabs/oras" name = "github.com/deislabs/oras"
packages = [ packages = [
"pkg/auth", "pkg/auth",
@ -274,8 +270,8 @@
"pkg/oras", "pkg/oras",
] ]
pruneopts = "T" pruneopts = "T"
revision = "b285197778e05cd348abb8ff50faf0ef7e3554a2" revision = "7467008b2683c5eff5fb13fe91c5b17b2eec75a3"
version = "v0.6.0" version = "v0.7.0"
[[projects]] [[projects]]
digest = "1:6b014c67cb522566c30ef02116f9acb50cd60954708cf92c6654e2985696db18" digest = "1:6b014c67cb522566c30ef02116f9acb50cd60954708cf92c6654e2985696db18"
@ -923,12 +919,12 @@
version = "v1.5.2" version = "v1.5.2"
[[projects]] [[projects]]
digest = "1:c3498d1186a4f84897812aa2dccfbd5d805955846f2cd020aa384bf0b218e9e9" digest = "1:425d221445ea27aaad740ed99e2be7cb463528526e63f6c599ad7d28f7ecea45"
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
packages = ["."] packages = ["."]
pruneopts = "T" pruneopts = "T"
revision = "8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f" revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.1" version = "v1.4.2"
[[projects]] [[projects]]
digest = "1:674fedb5641490b913f0f01e4f97f3f578f7a1c5f106cd47cfd5394eca8155db" digest = "1:674fedb5641490b913f0f01e4f97f3f578f7a1c5f106cd47cfd5394eca8155db"
@ -1821,7 +1817,6 @@
"github.com/asaskevich/govalidator", "github.com/asaskevich/govalidator",
"github.com/containerd/containerd/content", "github.com/containerd/containerd/content",
"github.com/containerd/containerd/errdefs", "github.com/containerd/containerd/errdefs",
"github.com/containerd/containerd/reference",
"github.com/containerd/containerd/remotes", "github.com/containerd/containerd/remotes",
"github.com/deislabs/oras/pkg/auth", "github.com/deislabs/oras/pkg/auth",
"github.com/deislabs/oras/pkg/auth/docker", "github.com/deislabs/oras/pkg/auth/docker",
@ -1837,7 +1832,6 @@
"github.com/evanphx/json-patch", "github.com/evanphx/json-patch",
"github.com/gobwas/glob", "github.com/gobwas/glob",
"github.com/gosuri/uitable", "github.com/gosuri/uitable",
"github.com/gosuri/uitable/util/strutil",
"github.com/mattn/go-shellwords", "github.com/mattn/go-shellwords",
"github.com/opencontainers/go-digest", "github.com/opencontainers/go-digest",
"github.com/opencontainers/image-spec/specs-go", "github.com/opencontainers/image-spec/specs-go",

@ -44,11 +44,15 @@
[[constraint]] [[constraint]]
name = "github.com/deislabs/oras" name = "github.com/deislabs/oras"
version = "0.6.0" version = "0.7.0"
[[constraint]]
name = "github.com/containerd/containerd"
version = "1.3.0-beta.2"
[[constraint]] [[constraint]]
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
version = "1.3.0" version = "1.4.2"
[[constraint]] [[constraint]]
name = "github.com/docker/go-units" name = "github.com/docker/go-units"

293
KEYS

@ -329,3 +329,296 @@ QP4bc5To+ohqwuOLw6hRo0YLf15jTJknCDtfsgKQ6uiR7ai+z6fqoH3kycCCcsPc
Y2/8LdVLydI6o8cZJDEpEexPaA== Y2/8LdVLydI6o8cZJDEpEexPaA==
=vtJm =vtJm
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
pub rsa4096/0x1EF612347F8A9958 2016-07-25 [SC]
Key fingerprint = 49D0 9C86 C3DC 8DA3 F0A0 7622 1EF6 1234 7F8A 9958
uid [ultimate] Adam Reese <adam@reese.io>
sig 3 0x1EF612347F8A9958 2018-01-02 Adam Reese <adam@reese.io>
sig 3 0x1EF612347F8A9958 2016-07-25 Adam Reese <adam@reese.io>
sig 0x62F49E747D911B60 2018-12-12 Matt Butcher <matt.butcher@microsoft.com>
sig 0x461449C25E36B98E 2018-12-12 Matthew Farina <matt@mattfarina.com>
sig 0x2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein <prydonius@gmail.com>
uid [ultimate] Adam Reese <areese@deis.com>
sig 3 0x1EF612347F8A9958 2018-01-02 Adam Reese <adam@reese.io>
sig 3 0x1EF612347F8A9958 2016-07-25 Adam Reese <adam@reese.io>
sig 0x62F49E747D911B60 2018-12-12 Matt Butcher <matt.butcher@microsoft.com>
sig 0x461449C25E36B98E 2018-12-12 Matthew Farina <matt@mattfarina.com>
sig 0x2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein <prydonius@gmail.com>
uid [ultimate] Adam Reese <areesedesign@gmail.com>
sig 3 0x1EF612347F8A9958 2018-01-02 Adam Reese <adam@reese.io>
sig 3 0x1EF612347F8A9958 2016-07-25 Adam Reese <adam@reese.io>
sig 0x62F49E747D911B60 2018-12-12 Matt Butcher <matt.butcher@microsoft.com>
sig 0x461449C25E36B98E 2018-12-12 Matthew Farina <matt@mattfarina.com>
sig 0x2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein <prydonius@gmail.com>
sub rsa2048/0x21DD8DC880EBB474 2016-07-25 [E] [expires: 2024-07-23]
sig 0x1EF612347F8A9958 2016-07-25 Adam Reese <adam@reese.io>
sub rsa2048/0x06F35E60A7A18DD6 2016-07-25 [SA] [expires: 2024-07-23]
sig 0x1EF612347F8A9958 2016-07-25 Adam Reese <adam@reese.io>
sub rsa4096/0x2970B7F911395FDE 2018-01-09 [A]
sig 0x1EF612347F8A9958 2018-01-09 Adam Reese <adam@reese.io>
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
uuqLDN3Ydqkoa++5HoqIdeh9uW4oiiGnMAkvH24012GpiXN37T45cH8LnNj0XooN
tYuiY/ZoCTw4gawtMwSWSl/htDrkLQKiUSiKdZqBLVXO2wRSjLjFPJEkq+eIBBcc
hvzj2C4a0sZhk4W1gHESeeB8D6IblgMm4oLa8Fn+4YhiwvbK9Dgja/7iiICP8bbO
3/9smCWOnnsixho5Jkd5IXWF0+tcfHVR/l+M8bCf+c02IGE/mD7RMFcxv3jdFYU7
/HsbwU9fCDSCAgXszwM+232kALGeur0riRJ42X1RNOzh82cF1wzYyxHR8JZMtcIt
x2MWc/n2wJO3swXnItKI1Vy4fjRu2PcJiQIzBBABCAAdFiEEURHac98S2OgSykYv
LNu/uzeugioFAlwRQeYACgkQLNu/uzeugiotag//YwAVNMHNLmOeAzSOzEf6Z6yK
2WDgEhsUt9Ykhy2pSc2vUD7jIXSAPTJYI7yY1flDmOe3kEVXXeVcPYAli9Ii5Eq4
DYJBC0FGboMbzdwh8P8RZGnhusB9MSlXYi2DnWH+oKGS9dQFnhpzn/lm0nl8tpL3
FrnwhlshNpgYYqIa29yO1EHskiFVLD6pL1W/DM6lMFlmTMjRb+y8eyZtbpCIdrY7
uhDRVwvJPYegj34KR+8OMo1iDvbckee5AR1Dc8L44KmB3Nm9AgW9o+bEz/kYz5pz
Eyv9ibthtagBpxU8kyfSuwH1Z3X3qzgpq8QVNVH0Y/5+sWAOEi2NkCHR2z4W757f
HO16Xz5SZQ5jBrqNFm6u7CLy0X96Sr5FRcefZl/gjqlgNqNTt/iJP48nfKvrEkn2
1OlFKRFitEC3QOWDTeI4uDFgS/OIUQq7AgqAaxERR3/kbAaVh2R71AsYuXyD57dW
Eo9PJkle9gSMvWUqRC8/0eSR4zgrwirkmXNQVEj55l2Z5y1kWv95NB1kozJb4aJW
TboHAHzklAMK1Gw8AMkjsA5PZyXQGkM+kXzUvE4TLC3qnsr5w05yJT2teOaWGK3s
3ZOtu2WuvuOXo0qd7oBzkF9850LQ83wEuwIfc7XcbaB0pyb6B3EtyZ+pMlRrzREg
EiX+bB98Q3qhNopjIDy0I0FkYW0gUmVlc2UgPGFyZWVzZWRlc2lnbkBnbWFpbC5j
b20+iQJOBBMBCgA4AhsDAh4BAheAFiEESdCchsPcjaPwoHYiHvYSNH+KmVgFAlpL
+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
=HZO9
-----END PGP PUBLIC KEY BLOCK-----

@ -3,7 +3,7 @@ maintainers:
- bacongobbler - bacongobbler
- fibonacci1729 - fibonacci1729
- hickeyma - hickeyma
- jascott1 - jdolitsky
- mattfarina - mattfarina
- michelleN - michelleN
- prydonius - prydonius
@ -12,6 +12,7 @@ maintainers:
- thomastaylor312 - thomastaylor312
- viglesiasce - viglesiasce
emeritus: emeritus:
- jascott1
- migmartri - migmartri
- nebril - nebril
- seh - seh

@ -3,6 +3,7 @@
[![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm) [![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm)
[![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm)
[![GoDoc](https://godoc.org/helm.sh/helm?status.svg)](https://godoc.org/helm.sh/helm) [![GoDoc](https://godoc.org/helm.sh/helm?status.svg)](https://godoc.org/helm.sh/helm)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131)
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.

@ -52,6 +52,7 @@ will be overwritten, but other files will be left alone.
type createOptions struct { type createOptions struct {
starter string // --starter starter string // --starter
name string name string
starterDir string
} }
func newCreateCmd(out io.Writer) *cobra.Command { func newCreateCmd(out io.Writer) *cobra.Command {
@ -64,6 +65,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.starterDir = helmpath.DataPath("starters")
return o.run(out) return o.run(out)
}, },
} }
@ -87,7 +89,7 @@ func (o *createOptions) run(out io.Writer) error {
if o.starter != "" { if o.starter != "" {
// Create from the starter // Create from the starter
lstarter := filepath.Join(helmpath.Starters(), o.starter) lstarter := filepath.Join(o.starterDir, o.starter)
// If path is absolute, we dont want to prefix it with helm starters folder // If path is absolute, we dont want to prefix it with helm starters folder
if filepath.IsAbs(o.starter) { if filepath.IsAbs(o.starter) {
lstarter = o.starter lstarter = o.starter

@ -31,15 +31,14 @@ import (
) )
func TestCreateCmd(t *testing.T) { func TestCreateCmd(t *testing.T) {
defer ensure.HelmHome(t)()
cname := "testchart" cname := "testchart"
ensure.HelmHome(t) dir := ensure.TempDir(t)
defer ensure.CleanHomeDirs(t) defer testChdir(t, dir)()
defer testChdir(t, helmpath.CachePath())()
// Run a create // Run a create
if _, _, err := executeActionCommand("create " + cname); err != nil { if _, _, err := executeActionCommand("create " + cname); err != nil {
t.Errorf("Failed to run create: %s", err) t.Fatalf("Failed to run create: %s", err)
return
} }
// Test that the chart is there // Test that the chart is there
@ -63,22 +62,22 @@ func TestCreateCmd(t *testing.T) {
} }
func TestCreateStarterCmd(t *testing.T) { func TestCreateStarterCmd(t *testing.T) {
defer ensure.HelmHome(t)()
cname := "testchart" cname := "testchart"
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) os.MkdirAll(helmpath.CachePath(), 0755)
defer ensure.CleanHomeDirs(t)
defer testChdir(t, helmpath.CachePath())() defer testChdir(t, helmpath.CachePath())()
// Create a starter. // Create a starter.
starterchart := helmpath.Starters() starterchart := helmpath.DataPath("starters")
os.Mkdir(starterchart, 0755) os.MkdirAll(starterchart, 0755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
} else { } else {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0755); err != nil { if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
@ -128,23 +127,23 @@ func TestCreateStarterCmd(t *testing.T) {
func TestCreateStarterAbsoluteCmd(t *testing.T) { func TestCreateStarterAbsoluteCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
cname := "testchart" cname := "testchart"
// Create a starter. // Create a starter.
starterchart := helmpath.Starters() starterchart := helmpath.DataPath("starters")
os.Mkdir(starterchart, 0755) os.MkdirAll(starterchart, 0755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
} else { } else {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0755); err != nil { if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
os.MkdirAll(helmpath.CachePath(), 0755)
defer testChdir(t, helmpath.CachePath())() defer testChdir(t, helmpath.CachePath())()
starterChartPath := filepath.Join(starterchart, "starterchart") starterChartPath := filepath.Join(starterchart, "starterchart")

@ -58,13 +58,13 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
ChartPath: chartpath, ChartPath: chartpath,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
} }
if client.Verify { if client.Verify {
man.Verify = downloader.VerifyIfPossible man.Verify = downloader.VerifyIfPossible
} }
if settings.Debug {
man.Debug = true
}
return man.Build() return man.Build()
}, },
} }

@ -22,31 +22,26 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/provenance" "helm.sh/helm/pkg/provenance"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestDependencyBuildCmd(t *testing.T) { func TestDependencyBuildCmd(t *testing.T) {
defer resetEnv()() srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
if _, err := srv.CopyCharts("testdata/testcharts/*.tgz"); err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rootDir := srv.Root()
srv.LinkIndices()
chartname := "depbuild" chartname := "depbuild"
if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil { createTestingChart(t, rootDir, chartname, srv.URL())
t.Fatal(err) repoFile := filepath.Join(rootDir, "repositories.yaml")
}
cmd := fmt.Sprintf("dependency build '%s'", filepath.Join(helmpath.DataPath(), chartname)) cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
@ -60,14 +55,14 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
// Make sure the actual file got downloaded. // Make sure the actual file got downloaded.
expect := filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz") expect := filepath.Join(rootDir, chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// In the second pass, we want to remove the chart's request dependency, // In the second pass, we want to remove the chart's request dependency,
// then see if it restores from the lock. // then see if it restores from the lock.
lockfile := filepath.Join(helmpath.DataPath(), chartname, "Chart.lock") lockfile := filepath.Join(rootDir, chartname, "Chart.lock")
if _, err := os.Stat(lockfile); err != nil { if _, err := os.Stat(lockfile); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -82,7 +77,6 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
// Now repeat the test that the dependency exists. // Now repeat the test that the dependency exists.
expect = filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -93,7 +87,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
i, err := repo.LoadIndexFile(helmpath.CacheIndex("test")) i, err := repo.LoadIndexFile(filepath.Join(rootDir, "index.yaml"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -63,13 +63,13 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
} }
if client.Verify { if client.Verify {
man.Verify = downloader.VerifyAlways man.Verify = downloader.VerifyAlways
} }
if settings.Debug {
man.Debug = true
}
return man.Update() return man.Update()
}, },
} }

@ -33,28 +33,31 @@ import (
) )
func TestDependencyUpdateCmd(t *testing.T) { func TestDependencyUpdateCmd(t *testing.T) {
defer resetEnv()() srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop()
copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) defer srv.Stop()
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
chartname := "depup" chartname := "depup"
ch := createTestingMetadata(chartname, srv.URL()) ch := createTestingMetadata(chartname, srv.URL())
md := ch.Metadata md := ch.Metadata
if err := chartutil.SaveDir(ch, helmpath.DataPath()); err != nil { if err := chartutil.SaveDir(ch, dir()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, out, err := executeActionCommand(fmt.Sprintf("dependency update '%s'", filepath.Join(helmpath.DataPath(), chartname))) _, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()),
)
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -66,7 +69,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
// Make sure the actual file got downloaded. // Make sure the actual file got downloaded.
expect := filepath.Join(helmpath.DataPath(), chartname, "charts/reqtest-0.1.0.tgz") expect := dir(chartname, "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -76,7 +79,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
i, err := repo.LoadIndexFile(helmpath.CacheIndex("test")) i, err := repo.LoadIndexFile(dir(helmpath.CacheIndexFile("test")))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -92,12 +95,11 @@ func TestDependencyUpdateCmd(t *testing.T) {
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()}, {Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
} }
dir := filepath.Join(helmpath.DataPath(), chartname, "Chart.yaml") if err := chartutil.SaveChartfile(dir(chartname, "Chart.yaml"), md); err != nil {
if err := chartutil.SaveChartfile(dir, md); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s'", filepath.Join(helmpath.DataPath(), chartname))) _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -105,11 +107,11 @@ func TestDependencyUpdateCmd(t *testing.T) {
// In this second run, we should see compressedchart-0.3.0.tgz, and not // In this second run, we should see compressedchart-0.3.0.tgz, and not
// the 0.1.0 version. // the 0.1.0 version.
expect = filepath.Join(helmpath.DataPath(), chartname, "charts/compressedchart-0.3.0.tgz") expect = dir(chartname, "charts/compressedchart-0.3.0.tgz")
if _, err := os.Stat(expect); err != nil { if _, err := os.Stat(expect); err != nil {
t.Fatalf("Expected %q: %s", expect, err) t.Fatalf("Expected %q: %s", expect, err)
} }
dontExpect := filepath.Join(helmpath.DataPath(), chartname, "charts/compressedchart-0.1.0.tgz") dontExpect := dir(chartname, "charts/compressedchart-0.1.0.tgz")
if _, err := os.Stat(dontExpect); err == nil { if _, err := os.Stat(dontExpect); err == nil {
t.Fatalf("Unexpected %q", dontExpect) t.Fatalf("Unexpected %q", dontExpect)
} }
@ -117,9 +119,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) { func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(helmpath.ConfigPath()) srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop() defer srv.Stop()
@ -131,11 +131,9 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
chartname := "depup" chartname := "depup"
if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil { createTestingChart(t, helmpath.DataPath(), chartname, srv.URL())
t.Fatal(err)
}
_, out, err := executeActionCommand(fmt.Sprintf("dependency update --skip-refresh %s", filepath.Join(helmpath.DataPath(), chartname))) _, out, err := executeActionCommand(fmt.Sprintf("dependency update --skip-refresh %s", helmpath.DataPath(chartname)))
if err == nil { if err == nil {
t.Fatal("Expected failure to find the repo with skipRefresh") t.Fatal("Expected failure to find the repo with skipRefresh")
} }
@ -148,25 +146,27 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t) srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
defer ensure.CleanHomeDirs(t)
srv := repotest.NewServer(helmpath.ConfigPath())
defer srv.Stop()
copied, err := srv.CopyCharts("testdata/testcharts/*.tgz")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) defer srv.Stop()
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
chartname := "depupdelete" if err := srv.LinkIndices(); err != nil {
if err := createTestingChart(helmpath.DataPath(), chartname, srv.URL()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s", filepath.Join(helmpath.DataPath(), chartname))) chartname := "depupdelete"
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
createTestingChart(t, dir(), chartname, srv.URL())
_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
@ -175,14 +175,14 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
_, output, err = executeActionCommand(fmt.Sprintf("dependency update %s", filepath.Join(helmpath.DataPath(), chartname))) _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
if err == nil { if err == nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")
} }
// Make sure charts dir still has dependencies // Make sure charts dir still has dependencies
files, err := ioutil.ReadDir(filepath.Join(filepath.Join(helmpath.DataPath(), chartname), "charts")) files, err := ioutil.ReadDir(filepath.Join(dir(chartname), "charts"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -198,7 +198,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
} }
// Make sure tmpcharts is deleted // Make sure tmpcharts is deleted
if _, err := os.Stat(filepath.Join(filepath.Join(helmpath.DataPath(), chartname), "tmpcharts")); !os.IsNotExist(err) { if _, err := os.Stat(filepath.Join(dir(chartname), "tmpcharts")); !os.IsNotExist(err) {
t.Fatalf("tmpcharts dir still exists") t.Fatalf("tmpcharts dir still exists")
} }
} }
@ -223,7 +223,10 @@ func createTestingMetadata(name, baseURL string) *chart.Chart {
// createTestingChart creates a basic chart that depends on reqtest-0.1.0 // createTestingChart creates a basic chart that depends on reqtest-0.1.0
// //
// The baseURL can be used to point to a particular repository server. // The baseURL can be used to point to a particular repository server.
func createTestingChart(dest, name, baseURL string) error { func createTestingChart(t *testing.T, dest, name, baseURL string) {
t.Helper()
cfile := createTestingMetadata(name, baseURL) cfile := createTestingMetadata(name, baseURL)
return chartutil.SaveDir(cfile, dest) if err := chartutil.SaveDir(cfile, dest); err != nil {
t.Fatal(err)
}
} }

@ -44,7 +44,7 @@ import (
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI") const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
var ( var (
settings cli.EnvSettings settings = cli.New()
config genericclioptions.RESTClientGetter config genericclioptions.RESTClientGetter
configOnce sync.Once configOnce sync.Once
) )

@ -42,11 +42,6 @@ func init() {
action.Timestamper = testTimestamper action.Timestamper = testTimestamper
} }
func TestMain(m *testing.M) {
exitCode := m.Run()
os.Exit(exitCode)
}
func runTestCmd(t *testing.T, tests []cmdTestCase) { func runTestCmd(t *testing.T, tests []cmdTestCase) {
t.Helper() t.Helper()
for _, tt := range tests { for _, tt := range tests {
@ -59,6 +54,7 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
t.Fatal(err) t.Fatal(err)
} }
} }
t.Log("running cmd: ", tt.cmd)
_, out, err := executeActionCommandC(storage, tt.cmd) _, out, err := executeActionCommandC(storage, tt.cmd)
if (err != nil) != tt.wantError { if (err != nil) != tt.wantError {
t.Errorf("expected error, got '%v'", err) t.Errorf("expected error, got '%v'", err)

@ -1,234 +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"
"io/ioutil"
"os"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin"
"helm.sh/helm/pkg/plugin/installer"
"helm.sh/helm/pkg/repo"
)
const initDesc = `
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
}
type pluginsFileEntry struct {
URL string `json:"url"`
Version string `json:"version,omitempty"`
}
type pluginsFile struct {
Plugins []*pluginsFileEntry `json:"plugins"`
}
func newInitCmd(out io.Writer) *cobra.Command {
o := &initOptions{}
cmd := &cobra.Command{
Use: "init",
Short: "initialize Helm client",
Long: initDesc,
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(out)
},
}
f := cmd.Flags()
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
f.StringVar(&o.pluginsFilename, "plugins", "", "a YAML file specifying plugins to install")
return cmd
}
// run initializes local config.
func (o *initOptions) run(out io.Writer) error {
if err := ensureDirectories(out); err != nil {
return err
}
if err := ensureReposFile(out, o.skipRefresh); err != nil {
return err
}
if err := ensureRepoFileFormat(helmpath.RepositoryFile(), out); err != nil {
return err
}
if o.pluginsFilename != "" {
if err := ensurePluginsInstalled(o.pluginsFilename, out); err != nil {
return err
}
}
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 the directories Helm uses exists.
//
// 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 directories {
if fi, err := os.Stat(p); err != nil {
fmt.Fprintf(out, "Creating %s \n", p)
if err := os.MkdirAll(p, 0755); err != nil {
return errors.Wrapf(err, "could not create %s", p)
}
} else if !fi.IsDir() {
return errors.Errorf("%s must be a directory", p)
}
}
return nil
}
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()
if err := f.WriteFile(repoFile, 0644); err != nil {
return err
}
} else if fi.IsDir() {
return errors.Errorf("%s must be a file, not a directory", repoFile)
}
return nil
}
func ensureRepoFileFormat(file string, out io.Writer) error {
r, err := repo.LoadFile(file)
if err == repo.ErrRepoOutOfDate {
fmt.Fprintln(out, "Updating repository file format...")
if err := r.WriteFile(file, 0644); err != nil {
return err
}
}
return nil
}
func ensurePluginsInstalled(pluginsFilename string, out io.Writer) error {
bytes, err := ioutil.ReadFile(pluginsFilename)
if err != nil {
return err
}
pf := new(pluginsFile)
if err := yaml.Unmarshal(bytes, &pf); err != nil {
return errors.Wrapf(err, "failed to parse %s", pluginsFilename)
}
for _, requiredPlugin := range pf.Plugins {
if err := ensurePluginInstalled(requiredPlugin, pluginsFilename, out); err != nil {
return errors.Wrapf(err, "failed to install plugin from %s", requiredPlugin.URL)
}
}
return nil
}
func ensurePluginInstalled(requiredPlugin *pluginsFileEntry, pluginsFilename string, out io.Writer) error {
i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version)
if err != nil {
return err
}
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
if err := installer.Install(i); err != nil {
return err
}
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
if err := runHook(p, plugin.Install); err != nil {
return err
}
fmt.Fprintf(out, "Installed plugin: %s\n", p.Metadata.Name)
} else if requiredPlugin.Version != "" {
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
if p.Metadata.Version != "" {
pluginVersion, err := semver.NewVersion(p.Metadata.Version)
if err != nil {
return err
}
constraint, err := semver.NewConstraint(requiredPlugin.Version)
if err != nil {
return err
}
if !constraint.Check(pluginVersion) {
fmt.Fprintf(out, "WARNING: Installed plugin '%s' is at version %s, while %s specifies %s\n",
p.Metadata.Name, p.Metadata.Version, pluginsFilename, requiredPlugin.Version)
}
}
}
return nil
}

@ -1,73 +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 (
"bytes"
"os"
"testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
)
const testPluginsFile = "testdata/plugins.yaml"
func TestEnsureHome(t *testing.T) {
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
b := bytes.NewBuffer(nil)
if err := ensureDirectories(b); err != nil {
t.Error(err)
}
if err := ensureReposFile(b, false); err != nil {
t.Error(err)
}
if err := ensureReposFile(b, true); err != nil {
t.Error(err)
}
if err := ensureRepoFileFormat(helmpath.RepositoryFile(), b); err != nil {
t.Error(err)
}
if err := ensurePluginsInstalled(testPluginsFile, b); err != nil {
t.Error(err)
}
expectedDirs := []string{helmpath.CachePath(), helmpath.ConfigPath(), helmpath.DataPath()}
for _, dir := range expectedDirs {
if fi, err := os.Stat(dir); err != nil {
t.Errorf("%s", err)
} else if !fi.IsDir() {
t.Errorf("%s is not a directory", fi)
}
}
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(helmpath.Plugins()); err != nil {
t.Error(err)
} else if len(plugins) != 1 {
t.Errorf("Expected 1 plugin, got %d", len(plugins))
} else if plugins[0].Metadata.Name != "testplugin" {
t.Errorf("Expected %s to be installed", "testplugin")
}
}

@ -174,7 +174,8 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
debug("CHART PATH: %s\n", cp) debug("CHART PATH: %s\n", cp)
vals, err := valueOpts.MergeValues(settings) p := getter.All(settings)
vals, err := valueOpts.MergeValues(p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,7 +202,9 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
ChartPath: cp, ChartPath: cp,
Keyring: client.ChartPathOptions.Keyring, Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false, SkipUpdate: false,
Getters: getter.All(settings), Getters: p,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
} }
if err := man.Update(); err != nil { if err := man.Update(); err != nil {
return nil, err return nil, err

@ -26,6 +26,7 @@ import (
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values" "helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/getter"
) )
var longLintHelp = ` var longLintHelp = `
@ -51,7 +52,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
paths = args paths = args
} }
client.Namespace = getNamespace() client.Namespace = getNamespace()
vals, err := valueOpts.MergeValues(settings) vals, err := valueOpts.MergeValues(getter.All(settings))
if err != nil { if err != nil {
return err return err
} }

@ -26,7 +26,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
@ -42,7 +41,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
return return
} }
found, err := findPlugins(helmpath.Plugins()) found, err := findPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return return

@ -62,7 +62,10 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return errors.New("--keyring is required for signing a package") return errors.New("--keyring is required for signing a package")
} }
} }
vals, err := valueOpts.MergeValues(settings) client.RepositoryConfig = settings.RepositoryConfig
client.RepositoryCache = settings.RepositoryCache
p := getter.All(settings)
vals, err := valueOpts.MergeValues(p)
if err != nil { if err != nil {
return err return err
} }
@ -78,8 +81,10 @@ func newPackageCmd(out io.Writer) *cobra.Command {
Out: ioutil.Discard, Out: ioutil.Discard,
ChartPath: path, ChartPath: path,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: p,
Debug: settings.Debug, Debug: settings.Debug,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
} }
if err := downloadManager.Update(); err != nil { if err := downloadManager.Update(); err != nil {

@ -32,19 +32,14 @@ import (
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
) )
func TestPackage(t *testing.T) { func TestPackage(t *testing.T) {
statExe := "stat"
statFileMsg := "no such file or directory" statFileMsg := "no such file or directory"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
statExe = "FindFirstFile"
statFileMsg = "The system cannot find the file specified." statFileMsg = "The system cannot find the file specified."
} }
defer resetEnv()()
tests := []struct { tests := []struct {
name string name string
flags map[string]string flags map[string]string
@ -100,13 +95,6 @@ func TestPackage(t *testing.T) {
expect: "", expect: "",
hasfile: "toot/alpine-0.1.0.tgz", hasfile: "toot/alpine-0.1.0.tgz",
}, },
{
name: "package --destination does-not-exist",
args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"destination": "does-not-exist"},
expect: fmt.Sprintf("failed to save: %s does-not-exist: %s", statExe, statFileMsg),
err: true,
},
{ {
name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine", name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
@ -139,18 +127,16 @@ func TestPackage(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ensure.HelmHome(t) for _, tt := range tests {
defer ensure.CleanHomeDirs(t) t.Run(tt.name, func(t *testing.T) {
t.Logf("Running tests in %s", helmpath.CachePath()) cachePath := ensure.TempDir(t)
defer testChdir(t, helmpath.CachePath())() defer testChdir(t, cachePath)()
if err := os.Mkdir("toot", 0777); err != nil { if err := os.MkdirAll("toot", 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var buf bytes.Buffer
for _, tt := range tests { c := newPackageCmd(&buf)
buf := bytes.NewBuffer(nil)
c := newPackageCmd(buf)
// This is an unfortunate byproduct of the tmpdir // This is an unfortunate byproduct of the tmpdir
if v, ok := tt.flags["keyring"]; ok && len(v) > 0 { if v, ok := tt.flags["keyring"]; ok && len(v) > 0 {
@ -168,10 +154,9 @@ func TestPackage(t *testing.T) {
err := c.RunE(c, adjustedArgs) err := c.RunE(c, adjustedArgs)
if err != nil { if err != nil {
if tt.err && re.MatchString(err.Error()) { if tt.err && re.MatchString(err.Error()) {
continue return
} }
t.Errorf("%q: expected error %q, got %q", tt.name, tt.expect, err) t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err)
continue
} }
if !re.Match(buf.Bytes()) { if !re.Match(buf.Bytes()) {
@ -193,21 +178,19 @@ func TestPackage(t *testing.T) {
t.Errorf("%q: provenance file is empty", tt.name) t.Errorf("%q: provenance file is empty", tt.name)
} }
} }
})
} }
} }
func TestSetAppVersion(t *testing.T) { func TestSetAppVersion(t *testing.T) {
defer resetEnv()()
var ch *chart.Chart var ch *chart.Chart
expectedAppVersion := "app-version-foo" expectedAppVersion := "app-version-foo"
ensure.HelmHome(t) dir := ensure.TempDir(t)
defer ensure.CleanHomeDirs(t)
c := newPackageCmd(&bytes.Buffer{}) c := newPackageCmd(&bytes.Buffer{})
flags := map[string]string{ flags := map[string]string{
"destination": helmpath.CachePath(), "destination": dir,
"app-version": expectedAppVersion, "app-version": expectedAppVersion,
} }
setFlags(c, flags) setFlags(c, flags)
@ -215,7 +198,7 @@ func TestSetAppVersion(t *testing.T) {
t.Errorf("unexpected error %q", err) t.Errorf("unexpected error %q", err)
} }
chartPath := filepath.Join(helmpath.CachePath(), "alpine-0.1.0.tgz") chartPath := filepath.Join(dir, "alpine-0.1.0.tgz")
if fi, err := os.Stat(chartPath); err != nil { if fi, err := os.Stat(chartPath); err != nil {
t.Errorf("expected file %q, got err %q", chartPath, err) t.Errorf("expected file %q, got err %q", chartPath, err)
} else if fi.Size() == 0 { } else if fi.Size() == 0 {
@ -223,7 +206,7 @@ func TestSetAppVersion(t *testing.T) {
} }
ch, err := loader.Load(chartPath) ch, err := loader.Load(chartPath)
if err != nil { if err != nil {
t.Errorf("unexpected error loading packaged chart: %v", err) t.Fatalf("unexpected error loading packaged chart: %v", err)
} }
if ch.Metadata.AppVersion != expectedAppVersion { if ch.Metadata.AppVersion != expectedAppVersion {
t.Errorf("expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion) t.Errorf("expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion)
@ -233,6 +216,8 @@ func TestSetAppVersion(t *testing.T) {
func TestPackageValues(t *testing.T) { func TestPackageValues(t *testing.T) {
defer resetEnv()() defer resetEnv()()
repoFile := "testdata/helmhome/helm/repositories.yaml"
testCases := []struct { testCases := []struct {
desc string desc string
args []string args []string
@ -243,33 +228,32 @@ func TestPackageValues(t *testing.T) {
{ {
desc: "helm package, single values file", desc: "helm package, single values file",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"repository-config": repoFile},
valuefilesContents: []string{"Name: chart-name-foo"}, valuefilesContents: []string{"Name: chart-name-foo"},
expected: []string{"Name: chart-name-foo"}, expected: []string{"Name: chart-name-foo"},
}, },
{ {
desc: "helm package, multiple values files", desc: "helm package, multiple values files",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"repository-config": repoFile},
valuefilesContents: []string{"Name: chart-name-foo", "foo: bar"}, valuefilesContents: []string{"Name: chart-name-foo", "foo: bar"},
expected: []string{"Name: chart-name-foo", "foo: bar"}, expected: []string{"Name: chart-name-foo", "foo: bar"},
}, },
{ {
desc: "helm package, with set option", desc: "helm package, with set option",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"set": "Name=chart-name-foo"}, flags: map[string]string{"set": "Name=chart-name-foo", "repository-config": repoFile},
expected: []string{"Name: chart-name-foo"}, expected: []string{"Name: chart-name-foo"},
}, },
{ {
desc: "helm package, set takes precedence over value file", desc: "helm package, set takes precedence over value file",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
valuefilesContents: []string{"Name: chart-name-foo"}, valuefilesContents: []string{"Name: chart-name-foo"},
flags: map[string]string{"set": "Name=chart-name-bar"}, flags: map[string]string{"set": "Name=chart-name-bar", "repository-config": repoFile},
expected: []string{"Name: chart-name-bar"}, expected: []string{"Name: chart-name-bar"},
}, },
} }
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
for _, tc := range testCases { for _, tc := range testCases {
var files []string var files []string
for _, contents := range tc.valuefilesContents { for _, contents := range tc.valuefilesContents {
@ -283,27 +267,21 @@ func TestPackageValues(t *testing.T) {
t.Errorf("unexpected error parsing values: %q", err) t.Errorf("unexpected error parsing values: %q", err)
} }
runAndVerifyPackageCommandValues(t, tc.args, tc.flags, valueFiles, expected)
}
}
func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[string]string, valueFiles string, expected chartutil.Values) {
t.Helper()
outputDir := ensure.TempDir(t) outputDir := ensure.TempDir(t)
if len(flags) == 0 { if len(tc.flags) == 0 {
flags = make(map[string]string) tc.flags = make(map[string]string)
} }
flags["destination"] = outputDir tc.flags["destination"] = outputDir
if len(valueFiles) > 0 { if len(valueFiles) > 0 {
flags["values"] = valueFiles tc.flags["values"] = valueFiles
} }
cmd := newPackageCmd(&bytes.Buffer{}) cmd := newPackageCmd(&bytes.Buffer{})
setFlags(cmd, flags) setFlags(cmd, tc.flags)
if err := cmd.RunE(cmd, args); err != nil { if err := cmd.RunE(cmd, tc.args); err != nil {
t.Errorf("unexpected error: %q", err) t.Fatalf("unexpected error: %q", err)
} }
outputFile := filepath.Join(outputDir, "alpine-0.1.0.tgz") outputFile := filepath.Join(outputDir, "alpine-0.1.0.tgz")
@ -311,30 +289,28 @@ func runAndVerifyPackageCommandValues(t *testing.T, args []string, flags map[str
actual, err := getChartValues(outputFile) actual, err := getChartValues(outputFile)
if err != nil { if err != nil {
t.Errorf("unexpected error extracting chart values: %q", err) t.Fatalf("unexpected error extracting chart values: %q", err)
} }
verifyValues(t, actual, expected) verifyValues(t, actual, expected)
} }
}
func createValuesFile(t *testing.T, data string) string { func createValuesFile(t *testing.T, data string) string {
outputDir := ensure.TempDir(t) outputDir := ensure.TempDir(t)
outputFile := filepath.Join(outputDir, "values.yaml") outputFile := filepath.Join(outputDir, "values.yaml")
if err := ioutil.WriteFile(outputFile, []byte(data), 0755); err != nil { if err := ioutil.WriteFile(outputFile, []byte(data), 0644); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
return outputFile return outputFile
} }
func getChartValues(chartPath string) (chartutil.Values, error) { func getChartValues(chartPath string) (chartutil.Values, error) {
chart, err := loader.Load(chartPath) chart, err := loader.Load(chartPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return chart.Values, nil return chart.Values, nil
} }

@ -21,8 +21,6 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
) )
func newPluginListCmd(out io.Writer) *cobra.Command { func newPluginListCmd(out io.Writer) *cobra.Command {
@ -30,8 +28,8 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
Use: "list", Use: "list",
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
debug("pluginDirs: %s", helmpath.Plugins()) debug("pluginDirs: %s", settings.PluginsDirectory)
plugins, err := findPlugins(helmpath.Plugins()) plugins, err := findPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
@ -56,8 +55,8 @@ func (o *pluginRemoveOptions) complete(args []string) error {
} }
func (o *pluginRemoveOptions) run(out io.Writer) error { func (o *pluginRemoveOptions) run(out io.Writer) error {
debug("loading installed plugins from %s", helmpath.Plugins()) debug("loading installed plugins from %s", settings.PluginsDirectory)
plugins, err := findPlugins(helmpath.Plugins()) plugins, err := findPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }

@ -18,16 +18,11 @@ package main
import ( import (
"bytes" "bytes"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
"helm.sh/helm/pkg/plugin"
) )
func TestManuallyProcessArgs(t *testing.T) { func TestManuallyProcessArgs(t *testing.T) {
@ -63,25 +58,22 @@ func TestManuallyProcessArgs(t *testing.T) {
} }
func TestLoadPlugins(t *testing.T) { func TestLoadPlugins(t *testing.T) {
defer resetEnv()() settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
settings.RepositoryConfig = "testdata/helmhome/helm/repositories.yaml"
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") settings.RepositoryCache = "testdata/helmhome/helm/repository"
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
out := bytes.NewBuffer(nil) var (
cmd := &cobra.Command{} out bytes.Buffer
loadPlugins(cmd, out) cmd cobra.Command
)
loadPlugins(&cmd, &out)
envs := strings.Join([]string{ envs := strings.Join([]string{
"fullenv", "fullenv",
helmpath.Plugins() + "/fullenv", "testdata/helmhome/helm/plugins/fullenv",
helmpath.Plugins(), "testdata/helmhome/helm/plugins",
helmpath.CachePath(), "testdata/helmhome/helm/repositories.yaml",
helmpath.ConfigPath(), "testdata/helmhome/helm/repository",
helmpath.DataPath(),
helmpath.RepositoryFile(),
helmpath.RepositoryCache(),
os.Args[0], os.Args[0],
}, "\n") }, "\n")
@ -95,7 +87,7 @@ func TestLoadPlugins(t *testing.T) {
}{ }{
{"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}}, {"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}},
{"echo", "echo stuff", "This echos stuff", "hello\n", []string{}}, {"echo", "echo stuff", "This echos stuff", "hello\n", []string{}},
{"env", "env stuff", "show the env", helmpath.DataPath() + "\n", []string{}}, {"env", "env stuff", "show the env", "env\n", []string{}},
{"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}}, {"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}},
} }
@ -123,7 +115,7 @@ func TestLoadPlugins(t *testing.T) {
// tests until this is fixed // tests until this is fixed
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
if err := pp.RunE(pp, tt.args); err != nil { if err := pp.RunE(pp, tt.args); err != nil {
t.Errorf("Error running %s: %s", tt.use, err) t.Errorf("Error running %s: %+v", tt.use, err)
} }
if out.String() != tt.expect { if out.String() != tt.expect {
t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String()) t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
@ -133,9 +125,8 @@ func TestLoadPlugins(t *testing.T) {
} }
func TestLoadPlugins_HelmNoPlugins(t *testing.T) { func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
defer resetEnv()() settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
settings.RepositoryConfig = "testdata/helmhome/helm/repository"
os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
os.Setenv("HELM_NO_PLUGINS", "1") os.Setenv("HELM_NO_PLUGINS", "1")
@ -148,34 +139,3 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
t.Fatalf("Expected 0 plugins, got %d", len(plugins)) t.Fatalf("Expected 0 plugins, got %d", len(plugins))
} }
} }
func TestSetupEnv(t *testing.T) {
defer resetEnv()()
name := "pequod"
os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
base := filepath.Join(helmpath.Plugins(), name)
settings.Debug = true
defer func() {
settings.Debug = false
}()
plugin.SetupPluginEnv(settings, name, base)
for _, tt := range []struct {
name string
expect string
}{
{"HELM_PLUGIN_NAME", name},
{"HELM_PLUGIN_DIR", base},
{"HELM_DEBUG", "1"},
{"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)
}
}
}

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
"helm.sh/helm/pkg/plugin/installer" "helm.sh/helm/pkg/plugin/installer"
) )
@ -58,8 +57,8 @@ func (o *pluginUpdateOptions) complete(args []string) error {
func (o *pluginUpdateOptions) run(out io.Writer) error { func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
debug("loading installed plugins from %s", helmpath.Plugins()) debug("loading installed plugins from %s", settings.PluginsDirectory)
plugins, err := findPlugins(helmpath.Plugins()) plugins, err := findPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }

@ -21,19 +21,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestPullCmd(t *testing.T) { func TestPullCmd(t *testing.T) {
defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*") srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -47,7 +40,7 @@ func TestPullCmd(t *testing.T) {
// all flags will get "-d outdir" appended. // all flags will get "-d outdir" appended.
tests := []struct { tests := []struct {
name string name string
args []string args string
wantError bool wantError bool
failExpect string failExpect string
expectFile string expectFile string
@ -56,47 +49,47 @@ func TestPullCmd(t *testing.T) {
}{ }{
{ {
name: "Basic chart fetch", name: "Basic chart fetch",
args: []string{"test/signtest"}, args: "test/signtest",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
}, },
{ {
name: "Chart fetch with version", name: "Chart fetch with version",
args: []string{"test/signtest --version=0.1.0"}, args: "test/signtest --version=0.1.0",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
}, },
{ {
name: "Fail chart fetch with non-existent version", name: "Fail chart fetch with non-existent version",
args: []string{"test/signtest --version=99.1.0"}, args: "test/signtest --version=99.1.0",
wantError: true, wantError: true,
failExpect: "no such chart", failExpect: "no such chart",
}, },
{ {
name: "Fail fetching non-existent chart", name: "Fail fetching non-existent chart",
args: []string{"test/nosuchthing"}, args: "test/nosuchthing",
failExpect: "Failed to fetch", failExpect: "Failed to fetch",
wantError: true, wantError: true,
}, },
{ {
name: "Fetch and verify", name: "Fetch and verify",
args: []string{"test/signtest --verify --keyring testdata/helm-test-key.pub"}, args: "test/signtest --verify --keyring testdata/helm-test-key.pub",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
expectVerify: true, expectVerify: true,
}, },
{ {
name: "Fetch and fail verify", name: "Fetch and fail verify",
args: []string{"test/reqtest --verify --keyring testdata/helm-test-key.pub"}, args: "test/reqtest --verify --keyring testdata/helm-test-key.pub",
failExpect: "Failed to fetch provenance", failExpect: "Failed to fetch provenance",
wantError: true, wantError: true,
}, },
{ {
name: "Fetch and untar", name: "Fetch and untar",
args: []string{"test/signtest --untar --untardir signtest"}, args: "test/signtest --untar --untardir signtest",
expectFile: "./signtest", expectFile: "./signtest",
expectDir: true, expectDir: true,
}, },
{ {
name: "Fetch, verify, untar", name: "Fetch, verify, untar",
args: []string{"test/signtest --verify --keyring=testdata/helm-test-key.pub --untar --untardir signtest"}, args: "test/signtest --verify --keyring=testdata/helm-test-key.pub --untar --untardir signtest",
expectFile: "./signtest", expectFile: "./signtest",
expectDir: true, expectDir: true,
expectVerify: true, expectVerify: true,
@ -104,40 +97,42 @@ func TestPullCmd(t *testing.T) {
{ {
name: "Chart fetch using repo URL", name: "Chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
args: []string{"signtest --repo", srv.URL()}, args: "signtest --repo " + srv.URL(),
}, },
{ {
name: "Fail fetching non-existent chart on repo URL", name: "Fail fetching non-existent chart on repo URL",
args: []string{"someChart --repo", srv.URL()}, args: "someChart --repo " + srv.URL(),
failExpect: "Failed to fetch chart", failExpect: "Failed to fetch chart",
wantError: true, wantError: true,
}, },
{ {
name: "Specific version chart fetch using repo URL", name: "Specific version chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
args: []string{"signtest --version=0.1.0 --repo", srv.URL()}, args: "signtest --version=0.1.0 --repo " + srv.URL(),
}, },
{ {
name: "Specific version chart fetch using repo URL", name: "Specific version chart fetch using repo URL",
args: []string{"signtest --version=0.2.0 --repo", srv.URL()}, args: "signtest --version=0.2.0 --repo " + srv.URL(),
failExpect: "Failed to fetch chart version", failExpect: "Failed to fetch chart version",
wantError: true, wantError: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
outdir := filepath.Join(helmpath.DataPath(), "testout") t.Run(tt.name, func(t *testing.T) {
os.RemoveAll(outdir) outdir := srv.Root()
os.Mkdir(outdir, 0755) cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s ",
tt.args,
cmd := strings.Join(append(tt.args, "-d", "'"+outdir+"'"), " ") outdir,
_, out, err := executeActionCommand("fetch " + cmd) filepath.Join(outdir, "repositories.yaml"),
outdir,
)
_, out, err := executeActionCommand(cmd)
if err != nil { if err != nil {
if tt.wantError { if tt.wantError {
continue return
} }
t.Errorf("%q reported error: %s", tt.name, err) t.Fatalf("%q reported error: %s", tt.name, err)
continue
} }
if tt.expectVerify { if tt.expectVerify {
@ -158,5 +153,6 @@ func TestPullCmd(t *testing.T) {
if fi.IsDir() != tt.expectDir { if fi.IsDir() != tt.expectDir {
t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir)
} }
})
} }
} }

@ -38,7 +38,7 @@ Authenticate to a remote registry.
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var usernameOpt, passwordOpt string var usernameOpt, passwordOpt string
var passwordFromStdinOpt bool var passwordFromStdinOpt, insecureOpt bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "login [host]", Use: "login [host]",
@ -54,7 +54,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
return err return err
} }
return action.NewRegistryLogin(cfg).Run(out, hostname, username, password) return action.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt)
}, },
} }
@ -62,6 +62,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
f.StringVarP(&usernameOpt, "username", "u", "", "registry username") f.StringVarP(&usernameOpt, "username", "u", "", "registry username")
f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token") f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token")
f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin") f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin")
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs")
return cmd return cmd
} }

@ -18,7 +18,9 @@ package main
import ( import (
"io" "io"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
@ -48,3 +50,7 @@ func newRepoCmd(out io.Writer) *cobra.Command {
return cmd return cmd
} }
func isNotExist(err error) bool {
return os.IsNotExist(errors.Cause(err))
}

@ -19,13 +19,15 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
@ -34,11 +36,14 @@ type repoAddOptions struct {
url string url string
username string username string
password string password string
noupdate bool noUpdate bool
certFile string certFile string
keyFile string keyFile string
caFile string caFile string
repoFile string
repoCache string
} }
func newRepoAddCmd(out io.Writer) *cobra.Command { func newRepoAddCmd(out io.Writer) *cobra.Command {
@ -51,6 +56,8 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.url = args[1] o.url = args[1]
o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache
return o.run(out) return o.run(out)
}, },
@ -59,7 +66,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.username, "username", "", "chart repository username") f.StringVar(&o.username, "username", "", "chart repository username")
f.StringVar(&o.password, "password", "", "chart repository password") f.StringVar(&o.password, "password", "", "chart repository password")
f.BoolVar(&o.noupdate, "no-update", false, "raise error if repo is already registered") f.BoolVar(&o.noUpdate, "no-update", false, "raise error if repo is already registered")
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
@ -68,31 +75,28 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
} }
func (o *repoAddOptions) run(out io.Writer) error { func (o *repoAddOptions) run(out io.Writer) error {
if err := addRepository(o.name, o.url, o.username, o.password, o.certFile, o.keyFile, o.caFile, o.noupdate); err != nil { b, err := ioutil.ReadFile(o.repoFile)
if err != nil && !os.IsNotExist(err) {
return err return err
} }
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
return nil
}
func addRepository(name, url, username, password string, certFile, keyFile, caFile string, noUpdate bool) error { var f repo.File
f, err := repo.LoadFile(helmpath.RepositoryFile()) if err := yaml.Unmarshal(b, &f); err != nil {
if err != nil {
return err return err
} }
if noUpdate && f.Has(name) { if o.noUpdate && f.Has(o.name) {
return errors.Errorf("repository name (%s) already exists, please specify a different name", name) return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
} }
c := repo.Entry{ c := repo.Entry{
Name: name, Name: o.name,
URL: url, URL: o.url,
Username: username, Username: o.username,
Password: password, Password: o.password,
CertFile: certFile, CertFile: o.certFile,
KeyFile: keyFile, KeyFile: o.keyFile,
CAFile: caFile, CAFile: o.caFile,
} }
r, err := repo.NewChartRepository(&c, getter.All(settings)) r, err := repo.NewChartRepository(&c, getter.All(settings))
@ -100,11 +104,15 @@ func addRepository(name, url, username, password string, certFile, keyFile, caFi
return err return err
} }
if err := r.DownloadIndexFile(); err != nil { if _, err := r.DownloadIndexFile(); err != nil {
return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url) return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", o.url)
} }
f.Update(&c) f.Update(&c)
return f.WriteFile(helmpath.RepositoryFile(), 0644) if err := f.WriteFile(o.repoFile, 0644); err != nil {
return err
}
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
return nil
} }

@ -18,47 +18,27 @@ package main
import ( import (
"fmt" "fmt"
"os" "io/ioutil"
"path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure" "helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestRepoAddCmd(t *testing.T) { func TestRepoAddCmd(t *testing.T) {
defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
srv, err := repotest.NewTempServer("testdata/testserver/*.*") srv, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer srv.Stop() defer srv.Stop()
repoFile := helmpath.RepositoryFile() repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
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{{ tests := []cmdTestCase{{
name: "add a repository", name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s", srv.URL()), cmd: fmt.Sprintf("repo add test-name %s --repository-config %s", srv.URL(), repoFile),
golden: "output/repo-add.txt", golden: "output/repo-add.txt",
}} }}
@ -66,54 +46,43 @@ func TestRepoAddCmd(t *testing.T) {
} }
func TestRepoAdd(t *testing.T) { func TestRepoAdd(t *testing.T) {
defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*") ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer ts.Stop() defer ts.Stop()
repoFile := helmpath.RepositoryFile() repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
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" const testRepoName = "test-name"
if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", true); err != nil { o := &repoAddOptions{
name: testRepoName,
url: ts.URL(),
noUpdate: true,
repoFile: repoFile,
}
if err := o.run(ioutil.Discard); err != nil {
t.Error(err) t.Error(err)
} }
f, err := repo.LoadFile(helmpath.RepositoryFile()) f, err := repo.LoadFile(repoFile)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
} }
if !f.Has(testRepoName) { if !f.Has(testRepoName) {
t.Errorf("%s was not successfully inserted into %s", testRepoName, helmpath.RepositoryFile()) t.Errorf("%s was not successfully inserted into %s", testRepoName, repoFile)
} }
if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", false); err != nil { o.noUpdate = false
if err := o.run(ioutil.Discard); err != nil {
t.Errorf("Repository was not updated: %s", err) t.Errorf("Repository was not updated: %s", err)
} }
if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", false); err != nil { if err := o.run(ioutil.Discard); err != nil {
t.Errorf("Duplicate repository name was added") t.Errorf("Duplicate repository name was added")
} }
} }

@ -87,7 +87,7 @@ func index(dir, url, mergeTo string) error {
var i2 *repo.IndexFile var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) { if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile() i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0755) i2.WriteFile(mergeTo, 0644)
} else { } else {
i2, err = repo.LoadIndexFile(mergeTo) i2, err = repo.LoadIndexFile(mergeTo)
if err != nil { if err != nil {
@ -97,5 +97,5 @@ func index(dir, url, mergeTo string) error {
i.Merge(i2) i.Merge(i2)
} }
i.SortEntries() i.SortEntries()
return i.WriteFile(out, 0755) return i.WriteFile(out, 0644)
} }

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
@ -35,11 +34,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Short: "list chart repositories", Short: "list chart repositories",
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
f, err := repo.LoadFile(helmpath.RepositoryFile()) f, err := repo.LoadFile(settings.RepositoryConfig)
if err != nil { if isNotExist(err) || len(f.Repositories) == 0 {
return err
}
if len(f.Repositories) == 0 {
return errors.New("no repositories to show") return errors.New("no repositories to show")
} }
table := uitable.New() table := uitable.New()

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -29,49 +30,57 @@ import (
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
type repoRemoveOptions struct {
name string
repoFile string
repoCache string
}
func newRepoRemoveCmd(out io.Writer) *cobra.Command { func newRepoRemoveCmd(out io.Writer) *cobra.Command {
o := &repoRemoveOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "remove [NAME]", Use: "remove [NAME]",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Short: "remove a chart repository", Short: "remove a chart repository",
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return removeRepoLine(out, args[0]) o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache
o.name = args[0]
return o.run(out)
}, },
} }
return cmd return cmd
} }
func removeRepoLine(out io.Writer, name string) error { func (o *repoRemoveOptions) run(out io.Writer) error {
repoFile := helmpath.RepositoryFile() r, err := repo.LoadFile(o.repoFile)
r, err := repo.LoadFile(repoFile) if isNotExist(err) || len(r.Repositories) == 0 {
if err != nil { return errors.New("no repositories configured")
return err
} }
if !r.Remove(name) { if !r.Remove(o.name) {
return errors.Errorf("no repo named %q found", name) return errors.Errorf("no repo named %q found", o.name)
} }
if err := r.WriteFile(repoFile, 0644); err != nil { if err := r.WriteFile(o.repoFile, 0644); err != nil {
return err return err
} }
if err := removeRepoCache(name); err != nil { if err := removeRepoCache(o.repoCache, o.name); err != nil {
return err return err
} }
fmt.Fprintf(out, "%q has been removed from your repositories\n", name) fmt.Fprintf(out, "%q has been removed from your repositories\n", o.name)
return nil return nil
} }
func removeRepoCache(name string) error { func removeRepoCache(root, name string) error {
if _, err := os.Stat(helmpath.CacheIndex(name)); err == nil { idx := filepath.Join(root, helmpath.CacheIndexFile(name))
err = os.Remove(helmpath.CacheIndex(name)) if _, err := os.Stat(idx); os.IsNotExist(err) {
if err != nil {
return err
}
}
return nil return nil
} else if err != nil {
return errors.Wrapf(err, "can't remove index file %s", idx)
}
return os.Remove(idx)
} }

@ -19,6 +19,7 @@ package main
import ( import (
"bytes" "bytes"
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
@ -29,60 +30,57 @@ import (
) )
func TestRepoRemove(t *testing.T) { func TestRepoRemove(t *testing.T) {
defer resetEnv()()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*") ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer ts.Stop() defer ts.Stop()
repoFile := helmpath.RepositoryFile() rootDir := ensure.TempDir(t)
if _, err := os.Stat(repoFile); err != nil { repoFile := filepath.Join(rootDir, "repositories.yaml")
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" const testRepoName = "test-name"
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
if err := removeRepoLine(b, testRepoName); err == nil {
rmOpts := repoRemoveOptions{
name: testRepoName,
repoFile: repoFile,
repoCache: rootDir,
}
if err := rmOpts.run(os.Stderr); err == nil {
t.Errorf("Expected error removing %s, but did not get one.", testRepoName) t.Errorf("Expected error removing %s, but did not get one.", testRepoName)
} }
if err := addRepository(testRepoName, ts.URL(), "", "", "", "", "", true); err != nil { o := &repoAddOptions{
name: testRepoName,
url: ts.URL(),
repoFile: repoFile,
}
if err := o.run(os.Stderr); err != nil {
t.Error(err) t.Error(err)
} }
mf, _ := os.Create(helmpath.CacheIndex(testRepoName)) idx := filepath.Join(rootDir, helmpath.CacheIndexFile(testRepoName))
mf, _ := os.Create(idx)
mf.Close() mf.Close()
b.Reset() b.Reset()
if err := removeRepoLine(b, testRepoName); err != nil {
if err := rmOpts.run(b); err != nil {
t.Errorf("Error removing %s from repositories", testRepoName) t.Errorf("Error removing %s from repositories", testRepoName)
} }
if !strings.Contains(b.String(), "has been removed") { if !strings.Contains(b.String(), "has been removed") {
t.Errorf("Unexpected output: %s", b.String()) t.Errorf("Unexpected output: %s", b.String())
} }
if _, err := os.Stat(helmpath.CacheIndex(testRepoName)); err == nil { if _, err := os.Stat(idx); err == nil {
t.Errorf("Error cache file was not removed for repository %s", testRepoName) t.Errorf("Error cache file was not removed for repository %s", testRepoName)
} }
f, err := repo.LoadFile(helmpath.RepositoryFile()) f, err := repo.LoadFile(repoFile)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -26,7 +26,6 @@ import (
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
) )
@ -39,6 +38,7 @@ var errNoRepositories = errors.New("no repositories found. You must add one befo
type repoUpdateOptions struct { type repoUpdateOptions struct {
update func([]*repo.ChartRepository, io.Writer) update func([]*repo.ChartRepository, io.Writer)
repoFile string
} }
func newRepoUpdateCmd(out io.Writer) *cobra.Command { func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@ -51,6 +51,7 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
Long: updateDesc, Long: updateDesc,
Args: require.NoArgs, Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
return o.run(out) return o.run(out)
}, },
} }
@ -58,12 +59,8 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
} }
func (o *repoUpdateOptions) run(out io.Writer) error { func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(helmpath.RepositoryFile()) f, err := repo.LoadFile(o.repoFile)
if err != nil { if isNotExist(err) || len(f.Repositories) == 0 {
return err
}
if len(f.Repositories) == 0 {
return errNoRepositories return errNoRepositories
} }
var repos []*repo.ChartRepository var repos []*repo.ChartRepository
@ -86,7 +83,7 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
wg.Add(1) wg.Add(1)
go func(re *repo.ChartRepository) { go func(re *repo.ChartRepository) {
defer wg.Done() defer wg.Done()
if err := re.DownloadIndexFile(); err != nil { if _, err := re.DownloadIndexFile(); err != nil {
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err) fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
} else { } else {
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)

@ -19,12 +19,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os"
"strings" "strings"
"testing" "testing"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/internal/test/ensure" "helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
@ -32,29 +29,7 @@ import (
) )
func TestUpdateCmd(t *testing.T) { func TestUpdateCmd(t *testing.T) {
defer resetEnv()() var out bytes.Buffer
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. // Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently. // The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer) { updater := func(repos []*repo.ChartRepository, out io.Writer) {
@ -64,8 +39,9 @@ func TestUpdateCmd(t *testing.T) {
} }
o := &repoUpdateOptions{ o := &repoUpdateOptions{
update: updater, update: updater,
repoFile: "testdata/repositories.yaml",
} }
if err := o.run(out); err != nil { if err := o.run(&out); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -76,9 +52,7 @@ func TestUpdateCmd(t *testing.T) {
func TestUpdateCharts(t *testing.T) { func TestUpdateCharts(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
ts, err := repotest.NewTempServer("testdata/testserver/*.*") ts, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil { if err != nil {

@ -28,7 +28,7 @@ import (
const ( const (
bashCompletionFunc = ` bashCompletionFunc = `
__helm_override_flag_list=(--kubeconfig --kube-context --home --namespace -n) __helm_override_flag_list=(--kubeconfig --kube-context --namespace -n)
__helm_override_flags() __helm_override_flags()
{ {
local ${__helm_override_flag_list[*]##*-} two_word_of of var local ${__helm_override_flag_list[*]##*-} two_word_of of var
@ -65,6 +65,22 @@ __helm_list_releases()
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi fi
} }
__helm_list_repos()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local out
if out=$(helm repo list | tail +2 | cut -f1 2>/dev/null); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__helm_list_plugins()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local out
if out=$(helm plugin list | tail +2 | cut -f1 2>/dev/null); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__helm_custom_func() __helm_custom_func()
{ {
__helm_debug "${FUNCNAME[0]}: last_command is $last_command" __helm_debug "${FUNCNAME[0]}: last_command is $last_command"
@ -74,6 +90,14 @@ __helm_custom_func()
__helm_list_releases __helm_list_releases
return return
;; ;;
helm_repo_remove)
__helm_list_repos
return
;;
helm_plugin_remove | helm_plugin_update)
__helm_list_plugins
return
;;
*) *)
;; ;;
esac esac
@ -83,13 +107,7 @@ __helm_custom_func()
var globalUsage = `The Kubernetes package manager var globalUsage = `The Kubernetes package manager
To begin working with Helm, run the 'helm init' command: Common actions for Helm:
$ helm init
This will set up any necessary local configuration.
Common actions from this point include:
- helm search: search for charts - helm search: search for charts
- helm fetch: download a chart to your local directory to view - helm fetch: download a chart to your local directory to view
@ -154,7 +172,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newUpgradeCmd(actionConfig, out), newUpgradeCmd(actionConfig, out),
newCompletionCmd(out), newCompletionCmd(out),
newInitCmd(out),
newPluginCmd(out), newPluginCmd(out),
newVersionCmd(out), newVersionCmd(out),

@ -59,8 +59,7 @@ func TestRootCmd(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
for k, v := range tt.envars { for k, v := range tt.envars {
os.Setenv(k, v) os.Setenv(k, v)

@ -19,6 +19,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"path/filepath"
"strings" "strings"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
@ -47,6 +48,8 @@ type searchRepoOptions struct {
regexp bool regexp bool
version string version string
maxColWidth uint maxColWidth uint
repoFile string
repoCacheDir string
} }
func newSearchRepoCmd(out io.Writer) *cobra.Command { func newSearchRepoCmd(out io.Writer) *cobra.Command {
@ -57,6 +60,8 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
Short: "search repositories for a keyword in charts", Short: "search repositories for a keyword in charts",
Long: searchRepoDesc, Long: searchRepoDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
o.repoCacheDir = settings.RepositoryCache
return o.run(out, args) return o.run(out, args)
}, },
} }
@ -141,15 +146,15 @@ func (o *searchRepoOptions) formatSearchResults(res []*search.Result) string {
func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) { func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) {
// Load the repositories.yaml // Load the repositories.yaml
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := repo.LoadFile(o.repoFile)
if err != nil { if isNotExist(err) || len(rf.Repositories) == 0 {
return nil, err return nil, errors.New("no repositories configured")
} }
i := search.NewIndex() i := search.NewIndex()
for _, re := range rf.Repositories { for _, re := range rf.Repositories {
n := re.Name n := re.Name
f := helmpath.CacheIndex(n) f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n))
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
// TODO should print to stderr // TODO should print to stderr

@ -17,23 +17,17 @@ limitations under the License.
package main package main
import ( import (
"os"
"testing" "testing"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestSearchRepositoriesCmd(t *testing.T) { func TestSearchRepositoriesCmd(t *testing.T) {
defer resetEnv()() repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.DataHomeEnvVar, "testdata/helmhome")
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "search for 'maria', expect one match", name: "search for 'alpine', expect two matches",
cmd: "search repo maria", cmd: "search repo alpine",
golden: "output/search-single.txt", golden: "output/search-multiple.txt",
}, { }, {
name: "search for 'alpine', expect two matches", name: "search for 'alpine', expect two matches",
cmd: "search repo alpine", cmd: "search repo alpine",
@ -71,5 +65,13 @@ func TestSearchRepositoriesCmd(t *testing.T) {
cmd: "search repo alp[ --regexp", cmd: "search repo alp[ --regexp",
wantError: true, wantError: true,
}} }}
settings.Debug = true
defer func() { settings.Debug = false }()
for i := range tests {
tests[i].cmd += " --repository-config " + repoFile
tests[i].cmd += " --repository-cache " + repoCache
}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

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

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

@ -28,6 +28,7 @@ import (
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart/loader" "helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/cli/values" "helm.sh/helm/pkg/cli/values"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/storage/driver" "helm.sh/helm/pkg/storage/driver"
) )
@ -73,7 +74,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
vals, err := valueOpts.MergeValues(settings) vals, err := valueOpts.MergeValues(getter.All(settings))
if err != nil { if err != nil {
return err return err
} }

@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"path/filepath"
"sort" "sort"
auth "github.com/deislabs/oras/pkg/auth/docker" auth "github.com/deislabs/oras/pkg/auth/docker"
@ -60,7 +59,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
} }
// set defaults if fields are missing // set defaults if fields are missing
if client.authorizer == nil { if client.authorizer == nil {
credentialsFile := filepath.Join(helmpath.Registry(), CredentialsFileBasename) credentialsFile := helmpath.CachePath("registry", CredentialsFileBasename)
authClient, err := auth.NewClient(credentialsFile) authClient, err := auth.NewClient(credentialsFile)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,7 +81,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
cache, err := NewCache( cache, err := NewCache(
CacheOptDebug(client.debug), CacheOptDebug(client.debug),
CacheOptWriter(client.out), CacheOptWriter(client.out),
CacheOptRoot(filepath.Join(helmpath.Registry(), CacheRootDir)), CacheOptRoot(helmpath.CachePath("registry", CacheRootDir)),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,8 +92,8 @@ func NewClient(opts ...ClientOption) (*Client, error) {
} }
// Login logs into a registry // Login logs into a registry
func (c *Client) Login(hostname string, username string, password string) error { func (c *Client) Login(hostname string, username string, password string, insecure bool) error {
err := c.authorizer.Login(ctx(c.out, c.debug), hostname, username, password) err := c.authorizer.Login(ctx(c.out, c.debug), hostname, username, password, insecure)
if err != nil { if err != nil {
return err return err
} }

@ -124,11 +124,17 @@ func (suite *RegistryClientTestSuite) TearDownSuite() {
} }
func (suite *RegistryClientTestSuite) Test_0_Login() { func (suite *RegistryClientTestSuite) Test_0_Login() {
err := suite.RegistryClient.Login(suite.DockerRegistryHost, "badverybad", "ohsobad") err := suite.RegistryClient.Login(suite.DockerRegistryHost, "badverybad", "ohsobad", false)
suite.NotNil(err, "error logging into registry with bad credentials") suite.NotNil(err, "error logging into registry with bad credentials")
err = suite.RegistryClient.Login(suite.DockerRegistryHost, testUsername, testPassword) err = suite.RegistryClient.Login(suite.DockerRegistryHost, "badverybad", "ohsobad", true)
suite.NotNil(err, "error logging into registry with bad credentials, insecure mode")
err = suite.RegistryClient.Login(suite.DockerRegistryHost, testUsername, testPassword, false)
suite.Nil(err, "no error logging into registry with good credentials") suite.Nil(err, "no error logging into registry with good credentials")
err = suite.RegistryClient.Login(suite.DockerRegistryHost, testUsername, testPassword, true)
suite.Nil(err, "no error logging into registry with good credentials, insecure mode")
} }
func (suite *RegistryClientTestSuite) Test_1_SaveChart() { func (suite *RegistryClientTestSuite) Test_1_SaveChart() {

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@ -51,7 +52,7 @@ func ParseReference(s string) (*Reference, error) {
// Split the components of the string on the colon or @, if it is more than 3, // Split the components of the string on the colon or @, if it is more than 3,
// immediately return an error. Other validation will be performed later in // immediately return an error. Other validation will be performed later in
// the function // the function
splitComponents := referenceDelimiter.Split(s, -1) splitComponents := fixSplitComponents(referenceDelimiter.Split(s, -1))
if len(splitComponents) > 3 { if len(splitComponents) > 3 {
return nil, errTooManyColons return nil, errTooManyColons
} }
@ -127,3 +128,18 @@ func (ref *Reference) validateNumColons() error {
func isValidPort(s string) bool { func isValidPort(s string) bool {
return validPortRegEx.MatchString(s) return validPortRegEx.MatchString(s)
} }
// fixSplitComponents this will modify reference parts based on presence of port
// Example: {localhost, 5000/x/y/z, 0.1.0} => {localhost:5000/x/y/z, 0.1.0}
func fixSplitComponents(c []string) []string {
if len(c) <= 1 {
return c
}
possiblePortParts := strings.Split(c[1], "/")
if _, err := strconv.Atoi(possiblePortParts[0]); err == nil {
components := []string{strings.Join(c[:2], ":")}
components = append(components, c[2:]...)
return components
}
return c
}

@ -94,4 +94,26 @@ func TestParseReference(t *testing.T) {
is.Equal("my.host.com/my/nested/repo", ref.Repo) is.Equal("my.host.com/my/nested/repo", ref.Repo)
is.Equal("1.2.3", ref.Tag) is.Equal("1.2.3", ref.Tag)
is.Equal("my.host.com/my/nested/repo:1.2.3", ref.FullName()) is.Equal("my.host.com/my/nested/repo:1.2.3", ref.FullName())
s = "localhost:5000/x/y/z"
ref, err = ParseReference(s)
is.NoError(err)
is.Equal("localhost:5000/x/y/z", ref.Repo)
is.Equal("", ref.Tag)
is.Equal("localhost:5000/x/y/z", ref.FullName())
s = "localhost:5000/x/y/z:123"
ref, err = ParseReference(s)
is.NoError(err)
is.Equal("localhost:5000/x/y/z", ref.Repo)
is.Equal("123", ref.Tag)
is.Equal("localhost:5000/x/y/z:123", ref.FullName())
s = "localhost:5000/x/y/z:123:x"
_, err = ParseReference(s)
is.Error(err, "ref contains too many colons (3)")
s = "localhost:5000/x/y/z:123:x:y"
_, err = ParseReference(s)
is.Error(err, "ref contains too many colons (4)")
} }

@ -35,12 +35,14 @@ import (
// Resolver resolves dependencies from semantic version ranges to a particular version. // Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct { type Resolver struct {
chartpath string chartpath string
cachepath string
} }
// New creates a new resolver for a given chart and a given helm home. // New creates a new resolver for a given chart and a given helm home.
func New(chartpath string) *Resolver { func New(chartpath, cachepath string) *Resolver {
return &Resolver{ return &Resolver{
chartpath: chartpath, chartpath: chartpath,
cachepath: cachepath,
} }
} }
@ -69,9 +71,11 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
} }
repoIndex, err := repo.LoadIndexFile(helmpath.CacheIndex(repoNames[d.Name])) idx := filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoNames[d.Name]))
repoIndex, err := repo.LoadIndexFile(idx)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") return nil, errors.Wrapf(err, "no cached repo found. (try 'helm repo update') %s", idx)
} }
vs, ok := repoIndex.Entries[d.Name] vs, ok := repoIndex.Entries[d.Name]

@ -16,15 +16,12 @@ limitations under the License.
package resolver package resolver
import ( import (
"os"
"testing" "testing"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "testdata")
tests := []struct { tests := []struct {
name string name string
req []*chart.Dependency req []*chart.Dependency
@ -91,7 +88,7 @@ func TestResolve(t *testing.T) {
} }
repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}
r := New("testdata/chartpath") r := New("testdata/chartpath", "testdata/repository")
for _, tt := range tests { for _, tt := range tests {
l, err := r.Resolve(tt.req, repoNames) l, err := r.Resolve(tt.req, repoNames)
if err != nil { if err != nil {

@ -21,55 +21,18 @@ import (
"os" "os"
"testing" "testing"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg" "helm.sh/helm/pkg/helmpath/xdg"
) )
// HelmHome sets up a Helm Home in a temp dir. // HelmHome sets up a Helm Home in a temp dir.
func HelmHome(t *testing.T) { func HelmHome(t *testing.T) func() {
t.Helper() t.Helper()
cachePath := TempDir(t) base := TempDir(t)
configPath := TempDir(t) os.Setenv(xdg.CacheHomeEnvVar, base)
dataPath := TempDir(t) os.Setenv(xdg.ConfigHomeEnvVar, base)
os.Setenv(xdg.CacheHomeEnvVar, cachePath) os.Setenv(xdg.DataHomeEnvVar, base)
os.Setenv(xdg.ConfigHomeEnvVar, configPath) return func() {
os.Setenv(xdg.DataHomeEnvVar, dataPath) os.RemoveAll(base)
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)
}
} }
} }

@ -57,6 +57,13 @@ func TestChartSave(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// TODO: guess latest based on semver?
_, err = action.cfg.RegistryClient.LoadChart(ref)
if err == nil {
t.Error("Expected error parsing ref without tag")
}
ref.Tag = "0.1.0"
if _, err := action.cfg.RegistryClient.LoadChart(ref); err != nil { if _, err := action.cfg.RegistryClient.LoadChart(ref); err != nil {
t.Error(err) t.Error(err)
} }

@ -37,7 +37,6 @@ import (
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
kubefake "helm.sh/helm/pkg/kube/fake" kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/releaseutil"
@ -563,8 +562,8 @@ OUTER:
// - if path is absolute or begins with '.', error out here // - if path is absolute or begins with '.', error out here
// - URL // - URL
// //
// If 'verify' is true, this will attempt to also verify the chart. // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart.
func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (string, error) { func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
version := strings.TrimSpace(c.Version) version := strings.TrimSpace(c.Version)
@ -591,6 +590,8 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(c.Username, c.Password), getter.WithBasicAuth(c.Username, c.Password),
}, },
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
} }
if c.Verify { if c.Verify {
dl.Verify = downloader.VerifyAlways dl.Verify = downloader.VerifyAlways
@ -604,11 +605,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (s
name = chartURL name = chartURL
} }
if _, err := os.Stat(helmpath.Archive()); os.IsNotExist(err) { if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil {
os.MkdirAll(helmpath.Archive(), 0744) return "", err
} }
filename, _, err := dl.DownloadTo(name, version, helmpath.Archive()) filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
if err == nil { if err == nil {
lname, err := filepath.Abs(filename) lname, err := filepath.Abs(filename)
if err != nil { if err != nil {

@ -43,6 +43,9 @@ type Package struct {
AppVersion string AppVersion string
Destination string Destination string
DependencyUpdate bool DependencyUpdate bool
RepositoryConfig string
RepositoryCache string
} }
// NewPackage creates a new Package object with the given configuration. // NewPackage creates a new Package object with the given configuration.
@ -131,7 +134,7 @@ func (p *Package) Clearsign(filename string) error {
return err return err
} }
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755) return ioutil.WriteFile(filename+".prov", []byte(sig), 0644)
} }
// promptUser implements provenance.PassphraseFetcher // promptUser implements provenance.PassphraseFetcher

@ -38,7 +38,7 @@ import (
type Pull struct { type Pull struct {
ChartPathOptions ChartPathOptions
Settings cli.EnvSettings // TODO: refactor this out of pkg/action Settings *cli.EnvSettings // TODO: refactor this out of pkg/action
Devel bool Devel bool
Untar bool Untar bool
@ -64,6 +64,8 @@ func (p *Pull) Run(chartRef string) (string, error) {
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(p.Username, p.Password), getter.WithBasicAuth(p.Username, p.Password),
}, },
RepositoryConfig: p.Settings.RepositoryConfig,
RepositoryCache: p.Settings.RepositoryCache,
} }
if p.Verify { if p.Verify {

@ -33,6 +33,6 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
} }
// Run executes the registry login operation // Run executes the registry login operation
func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string) error { func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, insecure bool) error {
return a.cfg.RegistryClient.Login(hostname, username, password) return a.cfg.RegistryClient.Login(hostname, username, password, insecure)
} }

@ -42,17 +42,19 @@ const (
// IgnorefileName is the name of the Helm ignore file. // IgnorefileName is the name of the Helm ignore file.
IgnorefileName = ".helmignore" IgnorefileName = ".helmignore"
// IngressFileName is the name of the example ingress file. // IngressFileName is the name of the example ingress file.
IngressFileName = "ingress.yaml" IngressFileName = TemplatesDir + sep + "ingress.yaml"
// DeploymentName is the name of the example deployment file. // DeploymentName is the name of the example deployment file.
DeploymentName = "deployment.yaml" DeploymentName = TemplatesDir + sep + "deployment.yaml"
// ServiceName is the name of the example service file. // ServiceName is the name of the example service file.
ServiceName = "service.yaml" ServiceName = TemplatesDir + sep + "service.yaml"
// NotesName is the name of the example NOTES.txt file. // NotesName is the name of the example NOTES.txt file.
NotesName = "NOTES.txt" NotesName = TemplatesDir + sep + "NOTES.txt"
// HelpersName is the name of the example NOTES.txt file. // HelpersName is the name of the example NOTES.txt file.
HelpersName = "_helpers.tpl" HelpersName = TemplatesDir + sep + "_helpers.tpl"
) )
const sep = string(filepath.Separator)
const defaultChartfile = `apiVersion: v2 const defaultChartfile = `apiVersion: v2
name: %s name: %s
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
@ -345,12 +347,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart.Templates = updatedTemplates schart.Templates = updatedTemplates
b, err := yaml.Marshal(schart.Values) b, err := yaml.Marshal(schart.Values)
if err != nil { if err != nil {
return err return errors.Wrap(err, "reading values file")
} }
var m map[string]interface{} var m map[string]interface{}
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
return err return errors.Wrap(err, "transforming values file")
} }
schart.Values = m schart.Values = m
@ -386,15 +388,6 @@ func Create(name, dir string) (string, error) {
if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
return cdir, errors.Errorf("file %s already exists and is not a directory", cdir) return cdir, errors.Errorf("file %s already exists and is not a directory", cdir)
} }
if err := os.MkdirAll(cdir, 0755); err != nil {
return cdir, err
}
for _, d := range []string{TemplatesDir, ChartsDir} {
if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
return cdir, err
}
}
files := []struct { files := []struct {
path string path string
@ -417,27 +410,27 @@ func Create(name, dir string) (string, error) {
}, },
{ {
// ingress.yaml // ingress.yaml
path: filepath.Join(cdir, TemplatesDir, IngressFileName), path: filepath.Join(cdir, IngressFileName),
content: transform(defaultIngress, name), content: transform(defaultIngress, name),
}, },
{ {
// deployment.yaml // deployment.yaml
path: filepath.Join(cdir, TemplatesDir, DeploymentName), path: filepath.Join(cdir, DeploymentName),
content: transform(defaultDeployment, name), content: transform(defaultDeployment, name),
}, },
{ {
// service.yaml // service.yaml
path: filepath.Join(cdir, TemplatesDir, ServiceName), path: filepath.Join(cdir, ServiceName),
content: transform(defaultService, name), content: transform(defaultService, name),
}, },
{ {
// NOTES.txt // NOTES.txt
path: filepath.Join(cdir, TemplatesDir, NotesName), path: filepath.Join(cdir, NotesName),
content: transform(defaultNotes, name), content: transform(defaultNotes, name),
}, },
{ {
// _helpers.tpl // _helpers.tpl
path: filepath.Join(cdir, TemplatesDir, HelpersName), path: filepath.Join(cdir, HelpersName),
content: transform(defaultHelpers, name), content: transform(defaultHelpers, name),
}, },
} }
@ -447,7 +440,7 @@ func Create(name, dir string) (string, error) {
// File exists and is okay. Skip it. // File exists and is okay. Skip it.
continue continue
} }
if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { if err := writeFile(file.path, file.content); err != nil {
return cdir, err return cdir, err
} }
} }
@ -459,3 +452,10 @@ func Create(name, dir string) (string, error) {
func transform(src, replacement string) []byte { func transform(src, replacement string) []byte {
return []byte(strings.ReplaceAll(src, "<CHARTNAME>", replacement)) return []byte(strings.ReplaceAll(src, "<CHARTNAME>", replacement))
} }
func writeFile(name string, content []byte) error {
if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
return err
}
return ioutil.WriteFile(name, content, 0644)
}

@ -49,30 +49,20 @@ func TestCreate(t *testing.T) {
t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
} }
for _, d := range []string{TemplatesDir, ChartsDir} { for _, f := range []string{
if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { ChartfileName,
t.Errorf("Expected %s dir: %s", d, err) DeploymentName,
} else if !fi.IsDir() { HelpersName,
t.Errorf("Expected %s to be a directory.", d) IgnorefileName,
} NotesName,
} ServiceName,
TemplatesDir,
for _, f := range []string{ChartfileName, ValuesfileName, IgnorefileName} { ValuesfileName,
if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { } {
t.Errorf("Expected %s file: %s", f, err) if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
} else if fi.IsDir() {
t.Errorf("Expected %s to be a file.", f)
}
}
for _, f := range []string{NotesName, DeploymentName, ServiceName, HelpersName} {
if fi, err := os.Stat(filepath.Join(dir, TemplatesDir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err) t.Errorf("Expected %s file: %s", f, err)
} else if fi.IsDir() {
t.Errorf("Expected %s to be a file.", f)
} }
} }
} }
func TestCreateFrom(t *testing.T) { func TestCreateFrom(t *testing.T) {
@ -94,7 +84,6 @@ func TestCreateFrom(t *testing.T) {
} }
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name) c := filepath.Join(tdir, cf.Name)
mychart, err := loader.LoadDir(c) mychart, err := loader.LoadDir(c)
if err != nil { if err != nil {
@ -105,27 +94,13 @@ func TestCreateFrom(t *testing.T) {
t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
} }
for _, d := range []string{TemplatesDir, ChartsDir} { for _, f := range []string{
if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { ChartfileName,
t.Errorf("Expected %s dir: %s", d, err) ValuesfileName,
} else if !fi.IsDir() { filepath.Join(TemplatesDir, "placeholder.tpl"),
t.Errorf("Expected %s to be a directory.", d) } {
} if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
}
for _, f := range []string{ChartfileName, ValuesfileName} {
if fi, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err)
} else if fi.IsDir() {
t.Errorf("Expected %s to be a file.", f)
}
}
for _, f := range []string{"placeholder.tpl"} {
if fi, err := os.Stat(filepath.Join(dir, TemplatesDir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err) t.Errorf("Expected %s file: %s", f, err)
} else if fi.IsDir() {
t.Errorf("Expected %s to be a file.", f)
} }
} }
} }

@ -20,7 +20,6 @@ import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -52,13 +51,7 @@ func SaveDir(c *chart.Chart, dest string) error {
if c.Values != nil { if c.Values != nil {
vf := filepath.Join(outdir, ValuesfileName) vf := filepath.Join(outdir, ValuesfileName)
b, _ := yaml.Marshal(c.Values) b, _ := yaml.Marshal(c.Values)
if err := ioutil.WriteFile(vf, b, 0644); err != nil { if err := writeFile(vf, b); err != nil {
return err
}
}
for _, d := range []string{TemplatesDir, ChartsDir} {
if err := os.MkdirAll(filepath.Join(outdir, d), 0755); err != nil {
return err return err
} }
} }
@ -67,13 +60,7 @@ func SaveDir(c *chart.Chart, dest string) error {
for _, o := range [][]*chart.File{c.Templates, c.Files} { for _, o := range [][]*chart.File{c.Templates, c.Files} {
for _, f := range o { for _, f := range o {
n := filepath.Join(outdir, f.Name) n := filepath.Join(outdir, f.Name)
if err := writeFile(n, f.Data); err != nil {
d := filepath.Dir(n)
if err := os.MkdirAll(d, 0755); err != nil {
return err
}
if err := ioutil.WriteFile(n, f.Data, 0644); err != nil {
return err return err
} }
} }
@ -84,7 +71,7 @@ func SaveDir(c *chart.Chart, dest string) error {
for _, dep := range c.Dependencies() { for _, dep := range c.Dependencies() {
// Here, we write each dependency as a tar file. // Here, we write each dependency as a tar file.
if _, err := Save(dep, base); err != nil { if _, err := Save(dep, base); err != nil {
return err return errors.Wrapf(err, "saving %s", dep.ChartFullPath())
} }
} }
return nil return nil
@ -99,13 +86,6 @@ func SaveDir(c *chart.Chart, dest string) error {
// //
// This returns the absolute path to the chart archive file. // This returns the absolute path to the chart archive file.
func Save(c *chart.Chart, outDir string) (string, error) { func Save(c *chart.Chart, outDir string) (string, error) {
// Create archive
if fi, err := os.Stat(outDir); err != nil {
return "", err
} else if !fi.IsDir() {
return "", errors.Errorf("location %s is not a directory", outDir)
}
if err := c.Validate(); err != nil { if err := c.Validate(); err != nil {
return "", errors.Wrap(err, "chart validation") return "", errors.Wrap(err, "chart validation")
} }
@ -199,7 +179,7 @@ func writeToTar(out *tar.Writer, name string, body []byte) error {
// TODO: Do we need to create dummy parent directory names if none exist? // TODO: Do we need to create dummy parent directory names if none exist?
h := &tar.Header{ h := &tar.Header{
Name: name, Name: name,
Mode: 0755, Mode: 0644,
Size: int64(len(body)), Size: int64(len(body)),
} }
if err := out.WriteHeader(h); err != nil { if err := out.WriteHeader(h); err != nil {

@ -26,6 +26,8 @@ import (
"os" "os"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"helm.sh/helm/pkg/helmpath"
) )
// EnvSettings describes all of the environment settings. // EnvSettings describes all of the environment settings.
@ -38,6 +40,24 @@ type EnvSettings struct {
KubeContext string KubeContext string
// Debug indicates whether or not Helm is running in Debug mode. // Debug indicates whether or not Helm is running in Debug mode.
Debug bool Debug bool
// RegistryConfig is the path to the registry config file.
RegistryConfig string
// RepositoryConfig is the path to the repositories file.
RepositoryConfig string
// Repositoryache is the path to the repository cache directory.
RepositoryCache string
// PluginsDirectory is the path to the plugins directory.
PluginsDirectory string
}
func New() *EnvSettings {
return &EnvSettings{
PluginsDirectory: helmpath.DataPath("plugins"),
RegistryConfig: helmpath.ConfigPath("registry.json"),
RepositoryConfig: helmpath.ConfigPath("repositories.yaml"),
RepositoryCache: helmpath.CachePath("repository"),
}
} }
// AddFlags binds flags to the given flagset. // AddFlags binds flags to the given flagset.
@ -46,6 +66,10 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use") fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use")
fs.BoolVar(&s.Debug, "debug", false, "enable verbose output") fs.BoolVar(&s.Debug, "debug", false, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the repositories config file")
fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the repositories config file")
} }
// Init sets values from the environment. // Init sets values from the environment.
@ -59,6 +83,8 @@ func (s *EnvSettings) Init(fs *pflag.FlagSet) {
var envMap = map[string]string{ var envMap = map[string]string{
"debug": "HELM_DEBUG", "debug": "HELM_DEBUG",
"namespace": "HELM_NAMESPACE", "namespace": "HELM_NAMESPACE",
"registry-config": "HELM_REGISTRY_CONFIG",
"repository-config": "HELM_REPOSITORY_CONFIG",
} }
func setFlagFromEnv(name, envar string, fs *pflag.FlagSet) { func setFlagFromEnv(name, envar string, fs *pflag.FlagSet) {

@ -25,7 +25,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/strvals" "helm.sh/helm/pkg/strvals"
) )
@ -38,14 +37,14 @@ type Options struct {
// MergeValues merges values from files specified via -f/--values and // MergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML // directly via --set or --set-string, marshaling them to YAML
func (opts *Options) MergeValues(settings cli.EnvSettings) (map[string]interface{}, error) { func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
base := map[string]interface{}{} base := map[string]interface{}{}
// User specified a values files via -f/--values // User specified a values files via -f/--values
for _, filePath := range opts.ValueFiles { for _, filePath := range opts.ValueFiles {
currentMap := map[string]interface{}{} currentMap := map[string]interface{}{}
bytes, err := readFile(filePath, settings) bytes, err := readFile(filePath, p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,24 +93,17 @@ func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
} }
// readFile load a file from stdin, the local directory, or a remote file with a url. // 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) { func readFile(filePath string, p getter.Providers) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" { if strings.TrimSpace(filePath) == "-" {
return ioutil.ReadAll(os.Stdin) return ioutil.ReadAll(os.Stdin)
} }
u, _ := url.Parse(filePath) u, _ := url.Parse(filePath)
p := getter.All(settings)
// FIXME: maybe someone handle other protocols like ftp. // FIXME: maybe someone handle other protocols like ftp.
getterConstructor, err := p.ByScheme(u.Scheme) g, err := p.ByScheme(u.Scheme)
if err != nil { if err != nil {
return ioutil.ReadFile(filePath) return ioutil.ReadFile(filePath)
} }
data, err := g.Get(filePath, getter.WithURL(filePath))
getter, err := getterConstructor(getter.WithURL(filePath))
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err return data.Bytes(), err
} }

@ -68,6 +68,8 @@ type ChartDownloader struct {
Getters getter.Providers Getters getter.Providers
// Options provide parameters to be passed along to the Getter being initialized. // Options provide parameters to be passed along to the Getter being initialized.
Options []getter.Option Options []getter.Option
RepositoryConfig string
RepositoryCache string
} }
// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file.
@ -87,17 +89,12 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return "", nil, err return "", nil, err
} }
constructor, err := c.Getters.ByScheme(u.Scheme) g, err := c.Getters.ByScheme(u.Scheme)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
g, err := constructor(c.Options...) data, err := g.Get(u.String(), c.Options...)
if err != nil {
return "", nil, err
}
data, err := g.Get(u.String())
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -157,7 +154,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
c.Options = append(c.Options, getter.WithURL(ref)) c.Options = append(c.Options, getter.WithURL(ref))
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := loadRepoConfig(c.RepositoryConfig)
if err != nil { if err != nil {
return u, err return u, err
} }
@ -218,7 +215,8 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
// Next, we need to load the index, and actually look up the chart. // Next, we need to load the index, and actually look up the chart.
i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name)) idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name))
i, err := repo.LoadIndexFile(idxFile)
if err != nil { if err != nil {
return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
} }
@ -337,7 +335,8 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry,
return nil, err return nil, err
} }
i, err := repo.LoadIndexFile(helmpath.CacheIndex(r.Config.Name)) idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name))
i, err := repo.LoadIndexFile(idxFile)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')")
} }
@ -355,3 +354,11 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry,
// This means that there is no repo file for the given URL. // This means that there is no repo file for the given URL.
return nil, ErrNoOwnerRepo return nil, ErrNoOwnerRepo
} }
func loadRepoConfig(file string) (*repo.File, error) {
r, err := repo.LoadFile(file)
if err != nil && !os.IsNotExist(errors.Cause(err)) {
return nil, err
}
return r, nil
}

@ -24,16 +24,16 @@ import (
"helm.sh/helm/internal/test/ensure" "helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/helmpath/xdg"
"helm.sh/helm/pkg/repo" "helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest" "helm.sh/helm/pkg/repo/repotest"
) )
func TestResolveChartRef(t *testing.T) { const (
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome") repoConfig = "testdata/repositories.yaml"
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome") repoCache = "testdata/repository"
)
func TestResolveChartRef(t *testing.T) {
tests := []struct { tests := []struct {
name, ref, expect, version string name, ref, expect, version string
fail bool fail bool
@ -57,7 +57,12 @@ func TestResolveChartRef(t *testing.T) {
c := ChartDownloader{ c := ChartDownloader{
Out: os.Stderr, Out: os.Stderr,
Getters: getter.All(cli.EnvSettings{}), RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
} }
for _, tt := range tests { for _, tt := range tests {
@ -105,16 +110,11 @@ func TestIsTar(t *testing.T) {
} }
func TestDownloadTo(t *testing.T) { func TestDownloadTo(t *testing.T) {
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
dest := helmpath.CachePath()
// Set up a fake repo with basic auth enabled // Set up a fake repo with basic auth enabled
srv := repotest.NewServer(helmpath.CachePath()) srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv.Stop() srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
@ -136,12 +136,18 @@ func TestDownloadTo(t *testing.T) {
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyAlways, Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub", Keyring: "testdata/helm-test-key.pub",
Getters: getter.All(cli.EnvSettings{}), RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth("username", "password"), getter.WithBasicAuth("username", "password"),
}, },
} }
cname := "/signtest-0.1.0.tgz" cname := "/signtest-0.1.0.tgz"
dest := srv.Root()
where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) where, v, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -161,10 +167,9 @@ func TestDownloadTo(t *testing.T) {
} }
func TestDownloadTo_VerifyLater(t *testing.T) { func TestDownloadTo_VerifyLater(t *testing.T) {
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
dest := helmpath.CachePath() dest := ensure.TempDir(t)
// Set up a fake repo // Set up a fake repo
srv, err := repotest.NewTempServer("testdata/*.tgz*") srv, err := repotest.NewTempServer("testdata/*.tgz*")
@ -179,7 +184,12 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
c := ChartDownloader{ c := ChartDownloader{
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyLater, Verify: VerifyLater,
Getters: getter.All(cli.EnvSettings{}), RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
} }
cname := "/signtest-0.1.0.tgz" cname := "/signtest-0.1.0.tgz"
where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) where, _, err := c.DownloadTo(srv.URL()+cname, "", dest)
@ -200,17 +210,19 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
} }
func TestScanReposForURL(t *testing.T) { func TestScanReposForURL(t *testing.T) {
os.Setenv(xdg.CacheHomeEnvVar, "testdata/helmhome")
os.Setenv(xdg.ConfigHomeEnvVar, "testdata/helmhome")
c := ChartDownloader{ c := ChartDownloader{
Out: os.Stderr, Out: os.Stderr,
Verify: VerifyLater, Verify: VerifyLater,
Getters: getter.All(cli.EnvSettings{}), RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
} }
u := "http://example.com/alpine-0.2.0.tgz" u := "http://example.com/alpine-0.2.0.tgz"
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := repo.LoadFile(repoConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -56,6 +56,8 @@ type Manager struct {
SkipUpdate bool SkipUpdate bool
// Getter collection for the operation // Getter collection for the operation
Getters []getter.Provider Getters []getter.Provider
RepositoryConfig string
RepositoryCache string
} }
// Build rebuilds a local charts directory from a lockfile. // Build rebuilds a local charts directory from a lockfile.
@ -94,11 +96,7 @@ func (m *Manager) Build() error {
} }
// Now we need to fetch every package here into charts/ // Now we need to fetch every package here into charts/
if err := m.downloadAll(lock.Dependencies); err != nil { return m.downloadAll(lock.Dependencies)
return err
}
return nil
} }
// Update updates a local charts directory. // Update updates a local charts directory.
@ -168,7 +166,7 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
// //
// This returns a lock file, which has all of the dependencies normalized to a specific version. // This returns a lock file, which has all of the dependencies normalized to a specific version.
func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
res := resolver.New(m.ChartPath) res := resolver.New(m.ChartPath, m.RepositoryCache)
return res.Resolve(req, repoNames) return res.Resolve(req, repoNames)
} }
@ -232,6 +230,8 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Out: m.Out, Out: m.Out,
Verify: m.Verify, Verify: m.Verify,
Keyring: m.Keyring, Keyring: m.Keyring,
RepositoryConfig: m.RepositoryConfig,
RepositoryCache: m.RepositoryCache,
Getters: m.Getters, Getters: m.Getters,
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(username, password), getter.WithBasicAuth(username, password),
@ -311,7 +311,7 @@ func (m *Manager) safeDeleteDep(name, dir string) error {
// hasAllRepos ensures that all of the referenced deps are in the local repo cache. // hasAllRepos ensures that all of the referenced deps are in the local repo cache.
func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { func (m *Manager) hasAllRepos(deps []*chart.Dependency) error {
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil { if err != nil {
return err return err
} }
@ -345,8 +345,11 @@ Loop:
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file. // getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file.
func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) { func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) {
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil { if err != nil {
if os.IsNotExist(err) {
return make(map[string]string), nil
}
return nil, err return nil, err
} }
repos := rf.Repositories repos := rf.Repositories
@ -412,7 +415,7 @@ repository, use "https://charts.example.com/" or "@example" instead of
// UpdateRepositories updates all of the local repos to the latest. // UpdateRepositories updates all of the local repos to the latest.
func (m *Manager) UpdateRepositories() error { func (m *Manager) UpdateRepositories() error {
rf, err := repo.LoadFile(helmpath.RepositoryFile()) rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil { if err != nil {
return err return err
} }
@ -427,8 +430,7 @@ func (m *Manager) UpdateRepositories() error {
} }
func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
out := m.Out fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...")
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
var wg sync.WaitGroup var wg sync.WaitGroup
for _, c := range repos { for _, c := range repos {
r, err := repo.NewChartRepository(c, m.Getters) r, err := repo.NewChartRepository(c, m.Getters)
@ -437,16 +439,16 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
} }
wg.Add(1) wg.Add(1)
go func(r *repo.ChartRepository) { go func(r *repo.ChartRepository) {
if err := r.DownloadIndexFile(); err != nil { if _, err := r.DownloadIndexFile(); err != nil {
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err)
} else { } else {
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name)
} }
wg.Done() wg.Done()
}(r) }(r)
} }
wg.Wait() wg.Wait()
fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈")
return nil return nil
} }
@ -549,17 +551,17 @@ func normalizeURL(baseURL, urlOrPath string) (string, error) {
// The key is the local name (which is only present in the repositories.yaml). // The key is the local name (which is only present in the repositories.yaml).
func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) {
indices := map[string]*repo.ChartRepository{} indices := map[string]*repo.ChartRepository{}
repoyaml := helmpath.RepositoryFile()
// Load repositories.yaml file // Load repositories.yaml file
rf, err := repo.LoadFile(repoyaml) rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil { if err != nil {
return indices, errors.Wrapf(err, "failed to load %s", repoyaml) return indices, errors.Wrapf(err, "failed to load %s", m.RepositoryConfig)
} }
for _, re := range rf.Repositories { for _, re := range rf.Repositories {
lname := re.Name lname := re.Name
index, err := repo.LoadIndexFile(helmpath.CacheIndex(lname)) idxFile := filepath.Join(m.RepositoryCache, helmpath.CacheIndexFile(lname))
index, err := repo.LoadIndexFile(idxFile)
if err != nil { if err != nil {
return indices, err return indices, err
} }

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

@ -76,7 +76,7 @@ func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
// Getter is an interface to support GET to the specified URL. // Getter is an interface to support GET to the specified URL.
type Getter interface { type Getter interface {
// Get file content by url string // Get file content by url string
Get(url string) (*bytes.Buffer, error) Get(url string, options ...Option) (*bytes.Buffer, error)
} }
// Constructor is the function for every getter which creates a specific instance // Constructor is the function for every getter which creates a specific instance
@ -108,41 +108,26 @@ type Providers []Provider
// ByScheme returns a Provider that handles the given scheme. // ByScheme returns a Provider that handles the given scheme.
// //
// If no provider handles this scheme, this will return an error. // If no provider handles this scheme, this will return an error.
func (p Providers) ByScheme(scheme string) (Constructor, error) { func (p Providers) ByScheme(scheme string) (Getter, error) {
for _, pp := range p { for _, pp := range p {
if pp.Provides(scheme) { if pp.Provides(scheme) {
return pp.New, nil return pp.New()
} }
} }
return nil, errors.Errorf("scheme %q not supported", scheme) return nil, errors.Errorf("scheme %q not supported", scheme)
} }
// All finds all of the registered getters as a list of Provider instances. var httpProvider = Provider{
// Currently, the built-in getters and the discovered plugins with downloader
// notations are collected.
func All(settings cli.EnvSettings) Providers {
result := Providers{
{
Schemes: []string{"http", "https"}, Schemes: []string{"http", "https"},
New: NewHTTPGetter, New: NewHTTPGetter,
},
} }
// All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader
// notations are collected.
func All(settings *cli.EnvSettings) Providers {
result := Providers{httpProvider}
pluginDownloaders, _ := collectPlugins(settings) pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...) result = append(result, pluginDownloaders...)
return result return result
} }
// ByScheme returns a getter for the given scheme.
//
// If the scheme is not supported, this will return an error.
func ByScheme(scheme string, settings cli.EnvSettings) (Provider, error) {
// Q: What do you call a scheme string who's the boss?
// A: Bruce Schemestring, of course.
a := All(settings)
for _, p := range a {
if p.Provides(scheme) {
return p, nil
}
}
return Provider{}, errors.Errorf("scheme %q not supported", scheme)
}

@ -16,13 +16,13 @@ limitations under the License.
package getter package getter
import ( import (
"os"
"testing" "testing"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath/xdg"
) )
const pluginDir = "testdata/plugins"
func TestProvider(t *testing.T) { func TestProvider(t *testing.T) {
p := Provider{ p := Provider{
[]string{"one", "three"}, []string{"one", "three"},
@ -53,9 +53,9 @@ func TestProviders(t *testing.T) {
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
os.Setenv(xdg.DataHomeEnvVar, "testdata") all := All(&cli.EnvSettings{
PluginsDirectory: pluginDir,
all := All(cli.EnvSettings{}) })
if len(all) != 3 { if len(all) != 3 {
t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all)) t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all))
} }
@ -66,12 +66,13 @@ func TestAll(t *testing.T) {
} }
func TestByScheme(t *testing.T) { func TestByScheme(t *testing.T) {
os.Setenv(xdg.DataHomeEnvVar, "testdata") g := All(&cli.EnvSettings{
PluginsDirectory: pluginDir,
if _, err := ByScheme("test", cli.EnvSettings{}); err != nil { })
if _, err := g.ByScheme("test"); err != nil {
t.Error(err) t.Error(err)
} }
if _, err := ByScheme("https", cli.EnvSettings{}); err != nil { if _, err := g.ByScheme("https"); err != nil {
t.Error(err) t.Error(err)
} }
} }

@ -34,7 +34,10 @@ type HTTPGetter struct {
} }
//Get performs a Get from repo.Getter and returns the body. //Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string) (*bytes.Buffer, error) { func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
}
return g.get(href) return g.get(href)
} }

@ -104,16 +104,11 @@ func TestDownload(t *testing.T) {
})) }))
defer srv.Close() defer srv.Close()
provider, err := ByScheme("http", cli.EnvSettings{}) g, err := All(new(cli.EnvSettings)).ByScheme("http")
if err != nil {
t.Fatal("No http provider found")
}
g, err := provider.New(WithURL(srv.URL))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
got, err := g.Get(srv.URL) got, err := g.Get(srv.URL, WithURL(srv.URL))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -25,14 +25,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin" "helm.sh/helm/pkg/plugin"
) )
// collectPlugins scans for getter plugins. // collectPlugins scans for getter plugins.
// This will load plugins according to the cli. // This will load plugins according to the cli.
func collectPlugins(settings cli.EnvSettings) (Providers, error) { func collectPlugins(settings *cli.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(helmpath.Plugins()) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,14 +56,17 @@ func collectPlugins(settings cli.EnvSettings) (Providers, error) {
// implemented in plugins. // implemented in plugins.
type pluginGetter struct { type pluginGetter struct {
command string command string
settings cli.EnvSettings settings *cli.EnvSettings
name string name string
base string base string
opts options opts options
} }
// Get runs downloader plugin command // Get runs downloader plugin command
func (p *pluginGetter) Get(href string) (*bytes.Buffer, error) { func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&p.opts)
}
commands := strings.Split(p.command, " ") commands := strings.Split(p.command, " ")
argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href) argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href)
prog := exec.Command(filepath.Join(p.base, commands[0]), argv...) prog := exec.Command(filepath.Join(p.base, commands[0]), argv...)
@ -84,7 +86,7 @@ func (p *pluginGetter) Get(href string) (*bytes.Buffer, error) {
} }
// NewPluginGetter constructs a valid plugin getter // NewPluginGetter constructs a valid plugin getter
func NewPluginGetter(command string, settings cli.EnvSettings, name, base string) Constructor { func NewPluginGetter(command string, settings *cli.EnvSettings, name, base string) Constructor {
return func(options ...Option) (Getter, error) { return func(options ...Option) (Getter, error) {
result := &pluginGetter{ result := &pluginGetter{
command: command, command: command,

@ -16,19 +16,17 @@ limitations under the License.
package getter package getter
import ( import (
"os"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath/xdg"
) )
func TestCollectPlugins(t *testing.T) { func TestCollectPlugins(t *testing.T) {
os.Setenv(xdg.DataHomeEnvVar, "testdata") env := &cli.EnvSettings{
PluginsDirectory: pluginDir,
env := cli.EnvSettings{} }
p, err := collectPlugins(env) p, err := collectPlugins(env)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -56,9 +54,9 @@ func TestPluginGetter(t *testing.T) {
t.Skip("TODO: refactor this test to work on windows") t.Skip("TODO: refactor this test to work on windows")
} }
os.Setenv(xdg.DataHomeEnvVar, "testdata") env := &cli.EnvSettings{
PluginsDirectory: pluginDir,
env := cli.EnvSettings{} }
pg := NewPluginGetter("echo", env, "test", ".") pg := NewPluginGetter("echo", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {
@ -82,11 +80,9 @@ func TestPluginSubCommands(t *testing.T) {
t.Skip("TODO: refactor this test to work on windows") t.Skip("TODO: refactor this test to work on windows")
} }
oldhh := os.Getenv("HELM_HOME") env := &cli.EnvSettings{
defer os.Setenv("HELM_HOME", oldhh) PluginsDirectory: pluginDir,
os.Setenv("HELM_HOME", "") }
env := cli.EnvSettings{}
pg := NewPluginGetter("echo -n", env, "test", ".") pg := NewPluginGetter("echo -n", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {

@ -13,69 +13,22 @@
package helmpath package helmpath
import (
"fmt"
"path/filepath"
)
// This helper builds paths to Helm's configuration, cache and data paths. // This helper builds paths to Helm's configuration, cache and data paths.
const lp = lazypath("helm") const lp = lazypath("helm")
// ConfigPath returns the path where Helm stores configuration. // ConfigPath returns the path where Helm stores configuration.
func ConfigPath() string { func ConfigPath(elem ...string) string { return lp.configPath(elem...) }
return lp.configPath("")
}
// CachePath returns the path where Helm stores cached objects. // CachePath returns the path where Helm stores cached objects.
func CachePath() string { func CachePath(elem ...string) string { return lp.cachePath(elem...) }
return lp.cachePath("")
}
// DataPath returns the path where Helm stores data. // DataPath returns the path where Helm stores data.
func DataPath() string { func DataPath(elem ...string) string { return lp.dataPath(elem...) }
return lp.dataPath("")
}
// Registry returns the path to the local registry cache.
func Registry() string {
return lp.cachePath("registry")
}
// RepositoryFile returns the path to the repositories.yaml file.
func RepositoryFile() string {
return lp.configPath("repositories.yaml")
}
// RepositoryCache returns the cache path for repository metadata.
func RepositoryCache() string {
return lp.cachePath("repository")
}
// CacheIndex returns the path to an index for the given named repository. // CacheIndex returns the path to an index for the given named repository.
func CacheIndex(name string) string { func CacheIndexFile(name string) string {
target := fmt.Sprintf("%s-index.yaml", name) if name != "" {
if name == "" { name += "-"
target = "index.yaml"
}
return filepath.Join(RepositoryCache(), target)
} }
return name + "index.yaml"
// Starters returns the path to the Helm starter packs.
func Starters() string {
return lp.dataPath("starters")
}
// PluginCache returns the cache path for plugins.
func PluginCache() string {
return lp.cachePath("plugins")
}
// Plugins returns the path to the plugins directory.
func Plugins() string {
return lp.dataPath("plugins")
}
// Archive returns the path to download chart archives.
func Archive() string {
return lp.cachePath("archive")
} }

@ -38,12 +38,6 @@ func TestHelmHome(t *testing.T) {
isEq(t, CachePath(), "/cache/helm") isEq(t, CachePath(), "/cache/helm")
isEq(t, ConfigPath(), "/config/helm") isEq(t, ConfigPath(), "/config/helm")
isEq(t, DataPath(), "/data/helm") isEq(t, DataPath(), "/data/helm")
isEq(t, RepositoryFile(), "/config/helm/repositories.yaml")
isEq(t, RepositoryCache(), "/cache/helm/repository")
isEq(t, CacheIndex("t"), "/cache/helm/repository/t-index.yaml")
isEq(t, CacheIndex(""), "/cache/helm/repository/index.yaml")
isEq(t, Starters(), "/data/helm/starters")
isEq(t, Archive(), "/cache/helm/archive")
// test to see if lazy-loading environment variables at runtime works // test to see if lazy-loading environment variables at runtime works
os.Setenv(xdg.CacheHomeEnvVar, "/cache2") os.Setenv(xdg.CacheHomeEnvVar, "/cache2")

@ -35,12 +35,6 @@ func TestHelmHome(t *testing.T) {
isEq(t, CachePath(), "c:\\helm") isEq(t, CachePath(), "c:\\helm")
isEq(t, ConfigPath(), "d:\\helm") isEq(t, ConfigPath(), "d:\\helm")
isEq(t, DataPath(), "e:\\helm") isEq(t, DataPath(), "e:\\helm")
isEq(t, RepositoryFile(), "d:\\helm\\repositories.yaml")
isEq(t, RepositoryCache(), "c:\\helm\\repository")
isEq(t, CacheIndex("t"), "c:\\helm\\repository\\t-index.yaml")
isEq(t, CacheIndex(""), "c:\\helm\\repository\\index.yaml")
isEq(t, Starters(), "e:\\helm\\starters")
isEq(t, Archive(), "c:\\helm\\archive")
// test to see if lazy-loading environment variables at runtime works // test to see if lazy-loading environment variables at runtime works
os.Setenv(xdg.CacheHomeEnvVar, "f:\\") os.Setenv(xdg.CacheHomeEnvVar, "f:\\")

@ -22,27 +22,27 @@ import (
// lazypath is an lazy-loaded path buffer for the XDG base directory specification. // lazypath is an lazy-loaded path buffer for the XDG base directory specification.
type lazypath string type lazypath string
func (l lazypath) path(envVar string, defaultFn func() string, file string) string { func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) string {
base := os.Getenv(envVar) base := os.Getenv(envVar)
if base == "" { if base == "" {
base = defaultFn() base = defaultFn()
} }
return filepath.Join(base, string(l), file) return filepath.Join(base, string(l), filepath.Join(elem...))
} }
// cachePath defines the base directory relative to which user specific non-essential data files // cachePath defines the base directory relative to which user specific non-essential data files
// should be stored. // should be stored.
func (l lazypath) cachePath(file string) string { func (l lazypath) cachePath(elem ...string) string {
return l.path(xdg.CacheHomeEnvVar, cacheHome, file) return l.path(xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
} }
// configPath defines the base directory relative to which user specific configuration files should // configPath defines the base directory relative to which user specific configuration files should
// be stored. // be stored.
func (l lazypath) configPath(file string) string { func (l lazypath) configPath(elem ...string) string {
return l.path(xdg.ConfigHomeEnvVar, configHome, file) return l.path(xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...))
} }
// dataPath defines the base directory relative to which user specific data files should be stored. // dataPath defines the base directory relative to which user specific data files should be stored.
func (l lazypath) dataPath(file string) string { func (l lazypath) dataPath(elem ...string) string {
return l.path(xdg.DataHomeEnvVar, dataHome, file) return l.path(xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
} }

@ -30,21 +30,19 @@ var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
// Key generates a cache key based on a url or scp string. The key is file // Key generates a cache key based on a url or scp string. The key is file
// system safe. // system safe.
func Key(repo string) (string, error) { func Key(repo string) (string, error) {
var (
var u *url.URL u *url.URL
var err error err error
var strip bool )
if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
// Match SCP-like syntax and convert it to a URL. // Match SCP-like syntax and convert it to a URL.
// Eg, "git@github.com:user/repo" becomes // Eg, "git@github.com:user/repo" becomes
// "ssh://git@github.com/user/repo". // "ssh://git@github.com/user/repo".
u = &url.URL{ u = &url.URL{
Scheme: "ssh",
User: url.User(m[1]), User: url.User(m[1]),
Host: m[2], Host: m[2],
Path: "/" + m[3], Path: "/" + m[3],
} }
strip = true
} else { } else {
u, err = url.Parse(repo) u, err = url.Parse(repo)
if err != nil { if err != nil {
@ -52,23 +50,18 @@ func Key(repo string) (string, error) {
} }
} }
if strip { var key strings.Builder
u.Scheme = ""
}
var key string
if u.Scheme != "" { if u.Scheme != "" {
key = u.Scheme + "-" key.WriteString(u.Scheme)
key.WriteString("-")
} }
if u.User != nil && u.User.Username() != "" { if u.User != nil && u.User.Username() != "" {
key = key + u.User.Username() + "-" key.WriteString(u.User.Username())
key.WriteString("-")
} }
key = key + u.Host key.WriteString(u.Host)
if u.Path != "" { if u.Path != "" {
key = key + strings.ReplaceAll(u.Path, "/", "-") key.WriteString(strings.ReplaceAll(u.Path, "/", "-"))
} }
return strings.ReplaceAll(key.String(), ":", "-"), nil
key = strings.ReplaceAll(key, ":", "-")
return key, nil
} }

@ -42,5 +42,5 @@ func (b *base) Path() string {
if b.Source == "" { if b.Source == "" {
return "" return ""
} }
return filepath.Join(helmpath.Plugins(), filepath.Base(b.Source)) return helmpath.DataPath("plugins", filepath.Base(b.Source))
} }

@ -79,18 +79,13 @@ func NewHTTPInstaller(source string) (*HTTPInstaller, error) {
return nil, err return nil, err
} }
getConstructor, err := getter.ByScheme("http", cli.EnvSettings{}) get, err := getter.All(new(cli.EnvSettings)).ByScheme("http")
if err != nil {
return nil, err
}
get, err := getConstructor.New(getter.WithURL(source))
if err != nil { if err != nil {
return nil, err return nil, err
} }
i := &HTTPInstaller{ i := &HTTPInstaller{
CacheDir: filepath.Join(helmpath.PluginCache(), key), CacheDir: helmpath.CachePath("plugins", key),
PluginName: stripPluginName(filepath.Base(source)), PluginName: stripPluginName(filepath.Base(source)),
base: newBase(source), base: newBase(source),
extractor: extractor, extractor: extractor,
@ -157,7 +152,7 @@ func (i HTTPInstaller) Path() string {
if i.base.Source == "" { if i.base.Source == "" {
return "" return ""
} }
return filepath.Join(helmpath.Plugins(), i.PluginName) return helmpath.DataPath("plugins", i.PluginName)
} }
// Extract extracts compressed archives // Extract extracts compressed archives

@ -19,12 +19,12 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/internal/test/ensure" "helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
@ -36,7 +36,9 @@ type TestHTTPGetter struct {
MockError error MockError error
} }
func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } func (t *TestHTTPGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) {
return t.MockResponse, t.MockError
}
// Fake plugin tarball data // Fake plugin tarball data
var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA="
@ -57,12 +59,11 @@ func TestStripName(t *testing.T) {
} }
func TestHTTPInstaller(t *testing.T) { func TestHTTPInstaller(t *testing.T) {
defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
} }
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
@ -90,7 +91,7 @@ func TestHTTPInstaller(t *testing.T) {
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Error(err)
} }
if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") { if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }
@ -104,12 +105,11 @@ func TestHTTPInstaller(t *testing.T) {
} }
func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) {
defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
} }
i, err := NewForSource(source, "0.0.2") i, err := NewForSource(source, "0.0.2")
@ -137,11 +137,10 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
func TestHTTPInstallerUpdate(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
} }
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
@ -169,7 +168,7 @@ func TestHTTPInstallerUpdate(t *testing.T) {
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Error(err)
} }
if i.Path() != filepath.Join(helmpath.Plugins(), "fake-plugin") { if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }

@ -18,7 +18,6 @@ package installer
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -43,14 +42,12 @@ type Installer interface {
// Install installs a plugin. // Install installs a plugin.
func Install(i Installer) error { func Install(i Installer) error {
if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) { if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
return errors.New(`plugin home "$XDG_CONFIG_HOME/helm/plugins" does not exist`) return err
} }
if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) {
return errors.New("plugin already exists") return errors.New("plugin already exists")
} }
return i.Install() return i.Install()
} }
@ -59,7 +56,6 @@ func Update(i Installer) error {
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
return errors.New("plugin does not exist") return errors.New("plugin does not exist")
} }
return i.Update() return i.Update()
} }

@ -21,16 +21,12 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/internal/test/ensure"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
var _ Installer = new(LocalInstaller) var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) { func TestLocalInstaller(t *testing.T) {
ensure.HelmHome(t)
defer ensure.CleanHomeDirs(t)
// Make a temp dir // Make a temp dir
tdir, err := ioutil.TempDir("", "helm-installer-") tdir, err := ioutil.TempDir("", "helm-installer-")
if err != nil { if err != nil {
@ -48,10 +44,10 @@ func TestLocalInstaller(t *testing.T) {
} }
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Fatal(err)
} }
if i.Path() != filepath.Join(helmpath.Plugins(), "echo") { if i.Path() != helmpath.DataPath("plugins", "echo") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
} }

@ -17,7 +17,6 @@ package installer // import "helm.sh/helm/pkg/plugin/installer"
import ( import (
"os" "os"
"path/filepath"
"sort" "sort"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
@ -53,7 +52,7 @@ func NewVCSInstaller(source, version string) (*VCSInstaller, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
cachedpath := filepath.Join(helmpath.PluginCache(), key) cachedpath := helmpath.CachePath("plugins", key)
repo, err := vcs.NewRepo(source, cachedpath) repo, err := vcs.NewRepo(source, cachedpath)
if err != nil { if err != nil {
return nil, err return nil, err

@ -49,11 +49,10 @@ func (r *testRepo) UpdateVersion(version string) error {
} }
func TestVCSInstaller(t *testing.T) { func TestVCSInstaller(t *testing.T) {
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
if err := os.MkdirAll(helmpath.Plugins(), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.Plugins(), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
} }
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
@ -83,7 +82,7 @@ func TestVCSInstaller(t *testing.T) {
if repo.current != "0.1.1" { if repo.current != "0.1.1" {
t.Errorf("expected version '0.1.1', got %q", repo.current) t.Errorf("expected version '0.1.1', got %q", repo.current)
} }
if i.Path() != filepath.Join(helmpath.Plugins(), "helm-env") { if i.Path() != helmpath.DataPath("plugins", "helm-env") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
@ -103,8 +102,7 @@ func TestVCSInstaller(t *testing.T) {
} }
func TestVCSInstallerNonExistentVersion(t *testing.T) { func TestVCSInstallerNonExistentVersion(t *testing.T) {
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
version := "0.2.0" version := "0.2.0"
@ -127,8 +125,7 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
} }
} }
func TestVCSInstallerUpdate(t *testing.T) { func TestVCSInstallerUpdate(t *testing.T) {
ensure.HelmHome(t) defer ensure.HelmHome(t)()
defer ensure.CleanHomeDirs(t)
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"

@ -25,7 +25,7 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
helm_env "helm.sh/helm/pkg/cli" "helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/helmpath" "helm.sh/helm/pkg/helmpath"
) )
@ -109,14 +109,15 @@ type Plugin struct {
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution // - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
// - If OS matches and there is no more specific match, the command will be prepared for execution // - If OS matches and there is no more specific match, the command will be prepared for execution
// - If no OS/Arch match is found, return nil // - If no OS/Arch match is found, return nil
func getPlatformCommand(platformCommands []PlatformCommand) []string { func getPlatformCommand(cmds []PlatformCommand) []string {
var command []string var command []string
for _, platformCommand := range platformCommands { eq := strings.EqualFold
if strings.EqualFold(platformCommand.OperatingSystem, runtime.GOOS) { for _, c := range cmds {
command = strings.Split(os.ExpandEnv(platformCommand.Command), " ") if eq(c.OperatingSystem, runtime.GOOS) {
command = strings.Split(os.ExpandEnv(c.Command), " ")
} }
if strings.EqualFold(platformCommand.OperatingSystem, runtime.GOOS) && strings.EqualFold(platformCommand.Architecture, runtime.GOARCH) { if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) {
return strings.Split(os.ExpandEnv(platformCommand.Command), " ") return strings.Split(os.ExpandEnv(c.Command), " ")
} }
} }
return command return command
@ -215,27 +216,20 @@ func FindPlugins(plugdirs string) ([]*Plugin, error) {
// SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because // SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because
// the plugin subsystem itself needs access to the environment variables // the plugin subsystem itself needs access to the environment variables
// created here. // created here.
func SetupPluginEnv(settings helm_env.EnvSettings, func SetupPluginEnv(settings *cli.EnvSettings, name, base string) {
shortName, base string) {
for key, val := range map[string]string{ for key, val := range map[string]string{
"HELM_PLUGIN_NAME": shortName, "HELM_PLUGIN_NAME": name,
"HELM_PLUGIN_DIR": base, "HELM_PLUGIN_DIR": base,
"HELM_BIN": os.Args[0], "HELM_BIN": os.Args[0],
"HELM_PLUGIN": helmpath.Plugins(), "HELM_PLUGIN": settings.PluginsDirectory,
// Set vars that convey common information. // Set vars that convey common information.
"HELM_PATH_REPOSITORY_FILE": helmpath.RepositoryFile(), "HELM_PATH_REPOSITORY_FILE": settings.RepositoryConfig,
"HELM_PATH_REPOSITORY_CACHE": helmpath.RepositoryCache(), "HELM_PATH_REPOSITORY_CACHE": settings.RepositoryCache,
"HELM_PATH_STARTER": helmpath.Starters(), "HELM_PATH_STARTER": helmpath.DataPath("starters"),
"HELM_PATH_CACHE": helmpath.CachePath(),
"HELM_PATH_CONFIG": helmpath.ConfigPath(),
"HELM_PATH_DATA": helmpath.DataPath(),
"HELM_HOME": helmpath.DataPath(), // for backwards compatibility with Helm 2 plugins "HELM_HOME": helmpath.DataPath(), // for backwards compatibility with Helm 2 plugins
"HELM_DEBUG": fmt.Sprint(settings.Debug),
} { } {
os.Setenv(key, val) os.Setenv(key, val)
} }
if settings.Debug {
os.Setenv("HELM_DEBUG", "1")
}
} }

@ -16,9 +16,13 @@ limitations under the License.
package plugin // import "helm.sh/helm/pkg/plugin" package plugin // import "helm.sh/helm/pkg/plugin"
import ( import (
"os"
"path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"testing" "testing"
"helm.sh/helm/pkg/cli"
) )
func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) {
@ -87,7 +91,6 @@ func TestPlatformPrepareCommand(t *testing.T) {
}, },
}, },
} }
argv := []string{"--debug", "--foo", "bar"}
var osStrCmp string var osStrCmp string
os := runtime.GOOS os := runtime.GOOS
arch := runtime.GOARCH arch := runtime.GOARCH
@ -101,6 +104,7 @@ func TestPlatformPrepareCommand(t *testing.T) {
osStrCmp = "os-arch" osStrCmp = "os-arch"
} }
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, osStrCmp, t) checkCommand(p, argv, osStrCmp, t)
} }
@ -116,7 +120,6 @@ func TestPartialPlatformPrepareCommand(t *testing.T) {
}, },
}, },
} }
argv := []string{"--debug", "--foo", "bar"}
var osStrCmp string var osStrCmp string
os := runtime.GOOS os := runtime.GOOS
arch := runtime.GOARCH arch := runtime.GOARCH
@ -128,6 +131,7 @@ func TestPartialPlatformPrepareCommand(t *testing.T) {
osStrCmp = "os-arch" osStrCmp = "os-arch"
} }
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, osStrCmp, t) checkCommand(p, argv, osStrCmp, t)
} }
@ -158,8 +162,7 @@ func TestNoMatchPrepareCommand(t *testing.T) {
} }
argv := []string{"--debug", "--foo", "bar"} argv := []string{"--debug", "--foo", "bar"}
_, _, err := p.PrepareCommand(argv) if _, _, err := p.PrepareCommand(argv); err == nil {
if err == nil {
t.Errorf("Expected error to be returned") t.Errorf("Expected error to be returned")
} }
} }
@ -251,3 +254,24 @@ func TestLoadAll(t *testing.T) {
t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name) t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name)
} }
} }
func TestSetupEnv(t *testing.T) {
name := "pequod"
base := filepath.Join("testdata/helmhome/helm/plugins", name)
s := &cli.EnvSettings{
PluginsDirectory: "testdata/helmhome/helm/plugins",
}
SetupPluginEnv(s, name, base)
for _, tt := range []struct {
name, expect string
}{
{"HELM_PLUGIN_NAME", name},
{"HELM_PLUGIN_DIR", base},
} {
if got := os.Getenv(tt.name); got != tt.expect {
t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got)
}
}
}

@ -5,9 +5,5 @@ echo "Hello from a Helm plugin"
echo "PARAMS" echo "PARAMS"
echo $* echo $*
echo "ENVIRONMENT" $HELM_BIN ls --all
echo $TILLER_HOST
echo $HELM_PATH_CONFIG
$HELM_BIN --host $TILLER_HOST ls --all

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

Loading…
Cancel
Save