diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 5e59c41ed..619148b83 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -32,6 +32,7 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/kube" kubefake "helm.sh/helm/v3/pkg/kube/fake" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage" @@ -108,9 +109,14 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) buf := new(bytes.Buffer) + fake := &kubefake.PrintingKubeClient{Out: ioutil.Discard} + actionConfig := &action.Configuration{ + Do: func(namespace string) kube.Interface { + return fake + }, Releases: store, - KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard}, + KubeClient: fake, Capabilities: chartutil.DefaultCapabilities, Log: func(format string, v ...interface{}) {}, } diff --git a/pkg/action/action.go b/pkg/action/action.go index ddd306f50..db99b2dfb 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -77,10 +77,14 @@ var ( // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) +type DoMake func(namespace string) kube.Interface + // Configuration injects the dependencies that all actions share. type Configuration struct { // RESTClientGetter is an interface that loads Kubernetes clients. - RESTClientGetter RESTClientGetter + RESTClientGetter genericclioptions.RESTClientGetter + + Do DoMake // Releases stores records of releases. Releases *storage.Storage @@ -365,7 +369,6 @@ func (c *Configuration) recordRelease(r *release.Release) { func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { kc := kube.New(getter) kc.Log = log - kc.Namespace = namespace lazyClient := &lazyClient{ namespace: namespace, @@ -413,6 +416,12 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac } c.RESTClientGetter = getter + c.Do = func(namespace string) kube.Interface { + client := kube.New(c.RESTClientGetter) + client.Log = c.Log + client.Namespace = namespace + return client + } c.KubeClient = kc c.Releases = store c.Log = log diff --git a/pkg/action/install.go b/pkg/action/install.go index 9bfecd915..99cda2a52 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -67,7 +67,8 @@ const defaultDirectoryPermission = 0755 // Install performs an installation operation. type Install struct { - cfg *Configuration + cfg *Configuration + client kube.Interface ChartPathOptions @@ -128,13 +129,13 @@ func (i *Install) installCRDs(crds []chart.CRD) error { totalItems := []*resource.Info{} for _, obj := range crds { // Read in the resources - res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) + res, err := i.client.Build(bytes.NewBuffer(obj.File.Data), false) if err != nil { return errors.Wrapf(err, "failed to install CRD %s", obj.Name) } // Send them to Kube - if _, err := i.cfg.KubeClient.Create(res); err != nil { + if _, err := i.client.Create(res); err != nil { // If the error is CRD already exists, continue. if apierrors.IsAlreadyExists(err) { crdName := res[0].Name @@ -156,7 +157,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { discoveryClient.Invalidate() // Give time for the CRD to be recognized. - if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { + if err := i.client.Wait(totalItems, 60*time.Second); err != nil { return err } @@ -170,9 +171,11 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // // If DryRun is set to true, this will prepare the release, but not install it func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { + i.client = i.cfg.Do(i.Namespace) + // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { - if err := i.cfg.KubeClient.IsReachable(); err != nil { + if err := i.client.IsReachable(); err != nil { return nil, err } } @@ -197,7 +200,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // NOTE(bacongobbler): used for `helm template` i.cfg.Capabilities = chartutil.DefaultCapabilities i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) - i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} + i.client = &kubefake.PrintingKubeClient{Out: ioutil.Discard} mem := driver.NewMemory() mem.SetNamespace(i.Namespace) @@ -252,7 +255,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. rel.SetStatus(release.StatusPendingInstall, "Initial install underway") var toBeAdopted kube.ResourceList - resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) + resources, err := i.client.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } @@ -299,11 +302,11 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. if err != nil { return nil, err } - resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) + resourceList, err := i.client.Build(bytes.NewBuffer(buf), true) if err != nil { return nil, err } - if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { + if _, err := i.client.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { return nil, err } } @@ -335,17 +338,17 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // do an update, but it's not clear whether we WANT to do an update if the re-use is set // to true, since that is basically an upgrade operation. if len(toBeAdopted) == 0 && len(resources) > 0 { - if _, err := i.cfg.KubeClient.Create(resources); err != nil { + if _, err := i.client.Create(resources); err != nil { return i.failRelease(rel, err) } } else if len(resources) > 0 { - if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { + if _, err := i.client.Update(toBeAdopted, resources, false); err != nil { return i.failRelease(rel, err) } } if i.Wait { - if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { + if err := i.client.Wait(resources, i.Timeout); err != nil { return i.failRelease(rel, err) } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 6c4012cfd..463dc76f0 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -31,6 +31,7 @@ import ( "helm.sh/helm/v3/internal/test" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/kube" kubefake "helm.sh/helm/v3/pkg/kube/fake" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" @@ -46,6 +47,9 @@ type nameTemplateTestCase struct { func installAction(t *testing.T) *Install { config := actionConfigFixture(t) instAction := NewInstall(config) + instAction.cfg.Do = func(namespace string) kube.Interface { + return config.KubeClient + } instAction.Namespace = "spaced" instAction.ReleaseName = "test-install-release" @@ -119,7 +123,7 @@ func TestInstallReleaseClientOnly(t *testing.T) { instAction.Run(buildChart(), nil) // disregard output is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) - is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) + is.Equal(instAction.client, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) } func TestInstallRelease_NoName(t *testing.T) { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index a7c20473e..de5358aee 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -21,11 +21,9 @@ import ( "io" "io/ioutil" "net/http" - "os" "strings" "testing" - "helm.sh/helm/v3/pkg/cli" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -255,84 +253,6 @@ func TestBuild(t *testing.T) { } } -func TestNamespace(t *testing.T) { - reset := func() func() { - var originalHelmNamespace *string - origEnv := os.Environ() - for _, pair := range origEnv { - kv := strings.SplitN(pair, "=", 2) - if kv[0] == "HELM_NAMESPACE" { - o := kv[1] - originalHelmNamespace = &o - break - } - } - - return func() { - os.Unsetenv("HELM_NAMESPACE") - if originalHelmNamespace != nil { - os.Setenv("HELM_NAMESPACE", *originalHelmNamespace) - } - } - } - - refstring := func(s string) *string { - return &s - } - tcs := []struct { - name string - envNamespace string - namespace *string - expectedNamespace string - }{ - { - name: "unset-namespace", - envNamespace: v1.NamespaceDefault, - namespace: nil, - expectedNamespace: v1.NamespaceDefault, - }, { - name: "test-namespace", - envNamespace: v1.NamespaceDefault, - namespace: refstring("test"), - expectedNamespace: "test", - }, { - name: "non-default-env-namespace", - envNamespace: "test", - namespace: nil, - expectedNamespace: "test", - }, { - name: "non-default-env-and-test-namespace", - envNamespace: "stage", - namespace: refstring("test"), - expectedNamespace: "test", - }, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - defer reset()() - - os.Setenv("HELM_NAMESPACE", tc.envNamespace) - c := New(cli.New().RESTClientGetter()) - if tc.namespace != nil { - c.Namespace = *tc.namespace - } - reslist, err := c.Build(strings.NewReader(testServiceManifest), true) - if err != nil { - t.Errorf("unexpected error from Build: %v", err) - } - if len(reslist) != 1 { - t.Errorf("expected 1 resource; got %d", len(reslist)) - } - res := reslist[0] - t.Logf("%#v\n", res) - if res.Namespace != tc.expectedNamespace { - t.Errorf("Incorrect namespace: expected '%s', got '%s'", tc.expectedNamespace, res.Namespace) - } - }) - } -} - func TestPerform(t *testing.T) { tests := []struct { name string