diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 98cb00f43..9203a7165 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -35,6 +35,7 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/gates" + "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" @@ -130,7 +131,9 @@ func loadReleasesInMemory(actionConfig *action.Configuration) { return } - actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} + actionConfig.GetKubeClient = func(namespace string) kube.Interface { + return &kubefake.PrintingKubeClient{Out: ioutil.Discard} + } for _, path := range filePaths { b, err := ioutil.ReadFile(path) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 619148b83..16963e877 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -112,11 +112,11 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) fake := &kubefake.PrintingKubeClient{Out: ioutil.Discard} actionConfig := &action.Configuration{ - Do: func(namespace string) kube.Interface { + GetKubeClient: func(namespace string) kube.Interface { return fake }, Releases: store, - KubeClient: fake, + KubeClient: nil, Capabilities: chartutil.DefaultCapabilities, Log: func(format string, v ...interface{}) {}, } diff --git a/pkg/action/action.go b/pkg/action/action.go index db99b2dfb..a688c73f9 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -77,19 +77,21 @@ 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 +// GetKubeClientFunc returns a Kube client with the specified namespace +type GetKubeClientFunc 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 genericclioptions.RESTClientGetter - Do DoMake + // GetKubeClient returns an instance of a Kubernetes API client. + GetKubeClient GetKubeClientFunc // Releases stores records of releases. Releases *storage.Storage - // KubeClient is a Kubernetes API client. + // Deprecated: KubeClient should not be used; use GetKubeClient instead KubeClient kube.Interface // RegistryClient is a client for working with registries @@ -416,13 +418,13 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac } c.RESTClientGetter = getter - c.Do = func(namespace string) kube.Interface { + c.GetKubeClient = func(namespace string) kube.Interface { client := kube.New(c.RESTClientGetter) client.Log = c.Log client.Namespace = namespace return client } - c.KubeClient = kc + c.KubeClient = nil c.Releases = store c.Log = log diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index fedf260fb..987745730 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -30,6 +30,7 @@ import ( "helm.sh/helm/v3/internal/experimental/registry" "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" @@ -80,9 +81,12 @@ func actionConfigFixture(t *testing.T) *Configuration { t.Fatal(err) } + fake := &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}} return &Configuration{ - Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, + Releases: storage.Init(driver.NewMemory()), + GetKubeClient: func(namespace string) kube.Interface { + return fake + }, Capabilities: chartutil.DefaultCapabilities, RegistryClient: registryClient, Log: func(format string, v ...interface{}) { diff --git a/pkg/action/get.go b/pkg/action/get.go index f44b53307..64c302061 100644 --- a/pkg/action/get.go +++ b/pkg/action/get.go @@ -39,7 +39,7 @@ func NewGet(cfg *Configuration) *Get { // Run executes 'helm get' against the given release. func (g *Get) Run(name string) (*release.Release, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { + if err := g.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go index 9c32db213..6c6b44a30 100644 --- a/pkg/action/get_values.go +++ b/pkg/action/get_values.go @@ -39,7 +39,7 @@ func NewGetValues(cfg *Configuration) *GetValues { // Run executes 'helm get values' against the given release. func (g *GetValues) Run(name string) (map[string]interface{}, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { + if err := g.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/history.go b/pkg/action/history.go index f4043609c..9c3a3e635 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -42,7 +42,7 @@ func NewHistory(cfg *Configuration) *History { // Run executes 'helm history' against the given release. func (h *History) Run(name string) ([]*release.Release, error) { - if err := h.cfg.KubeClient.IsReachable(); err != nil { + if err := h.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 40c1ffdb6..2988955fb 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -55,7 +55,8 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return err } - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) + client := cfg.GetKubeClient(rl.Namespace) + resources, err := client.Build(bytes.NewBufferString(h.Manifest), true) if err != nil { return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) } @@ -73,14 +74,14 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.LastRun.Phase = release.HookPhaseUnknown // Create hook resources - if _, err := cfg.KubeClient.Create(resources); err != nil { + if _, err := client.Create(resources); err != nil { h.LastRun.CompletedAt = helmtime.Now() h.LastRun.Phase = release.HookPhaseFailed return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) } // Watch hook resources until they have completed - err = cfg.KubeClient.WatchUntilReady(resources, timeout) + err = client.WatchUntilReady(resources, timeout) // Note the time of success/failure h.LastRun.CompletedAt = helmtime.Now() // Mark hook as succeeded or failed @@ -127,11 +128,12 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return nil } if hookHasDeletePolicy(h, policy) { - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) + client := cfg.GetKubeClient("") + resources, err := client.Build(bytes.NewBufferString(h.Manifest), false) if err != nil { return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) } - _, errs := cfg.KubeClient.Delete(resources) + _, errs := client.Delete(resources) if len(errs) > 0 { return errors.New(joinErrors(errs)) } diff --git a/pkg/action/install.go b/pkg/action/install.go index 99cda2a52..38415e19f 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -171,7 +171,7 @@ 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) + i.client = i.cfg.GetKubeClient(i.Namespace) // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 463dc76f0..d4fed924e 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -31,7 +31,6 @@ 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" @@ -47,9 +46,6 @@ 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" @@ -301,9 +297,9 @@ func TestInstallRelease_FailedHooks(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "failed-hooks" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := instAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WatchUntilReadyError = fmt.Errorf("Failed watch") - instAction.cfg.KubeClient = failer + // instAction.cfg.KubeClient = failer vals := map[string]interface{}{} res, err := instAction.Run(buildChart(), vals) @@ -354,9 +350,9 @@ func TestInstallRelease_Wait(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := instAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer + // instAction.cfg.KubeClient = failer instAction.Wait = true vals := map[string]interface{}{} @@ -372,9 +368,9 @@ func TestInstallRelease_Atomic(t *testing.T) { t.Run("atomic uninstall succeeds", func(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := instAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer + // instAction.cfg.KubeClient = failer instAction.Atomic = true vals := map[string]interface{}{} @@ -392,10 +388,10 @@ func TestInstallRelease_Atomic(t *testing.T) { t.Run("atomic uninstall fails", func(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away-with-me" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := instAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") failer.DeleteError = fmt.Errorf("uninstall fail") - instAction.cfg.KubeClient = failer + // instAction.cfg.KubeClient = failer instAction.Atomic = true vals := map[string]interface{}{} diff --git a/pkg/action/list.go b/pkg/action/list.go index 5ba0c4770..cf6b7f776 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -141,7 +141,7 @@ func NewList(cfg *Configuration) *List { // Run executes the list command, returning a set of matches. func (l *List) Run() ([]*release.Release, error) { - if err := l.cfg.KubeClient.IsReachable(); err != nil { + if err := l.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 2f6f5cfce..30d20d92c 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -48,7 +48,7 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { // Run executes 'helm test' against the given release. func (r *ReleaseTesting) Run(name string) (*release.Release, error) { - if err := r.cfg.KubeClient.IsReachable(); err != nil { + if err := r.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 542acefae..38501cdd0 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -55,7 +55,7 @@ func NewRollback(cfg *Configuration) *Rollback { // Run executes 'helm rollback' against the given release. func (r *Rollback) Run(name string) error { - if err := r.cfg.KubeClient.IsReachable(); err != nil { + if err := r.cfg.GetKubeClient("").IsReachable(); err != nil { return err } @@ -145,11 +145,13 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas return targetRelease, nil } - current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) + client := r.cfg.GetKubeClient(currentRelease.Namespace) + + current, err := client.Build(bytes.NewBufferString(currentRelease.Manifest), false) if err != nil { return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } - target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) + target, err := client.Build(bytes.NewBufferString(targetRelease.Manifest), false) if err != nil { return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } @@ -163,7 +165,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) } - results, err := r.cfg.KubeClient.Update(current, target, r.Force) + results, err := client.Update(current, target, r.Force) if err != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) @@ -175,7 +177,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.recordRelease(targetRelease) if r.CleanupOnFail { r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) - _, errs := r.cfg.KubeClient.Delete(results.Created) + _, errs := client.Delete(results.Created) if errs != nil { var errorList []string for _, e := range errs { @@ -199,7 +201,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } if r.Wait { - if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { + if err := client.Wait(target, r.Timeout); err != nil { targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease) diff --git a/pkg/action/status.go b/pkg/action/status.go index 1c556e28d..66fb40533 100644 --- a/pkg/action/status.go +++ b/pkg/action/status.go @@ -43,7 +43,7 @@ func NewStatus(cfg *Configuration) *Status { // Run executes 'helm status' against the given release. func (s *Status) Run(name string) (*release.Release, error) { - if err := s.cfg.KubeClient.IsReachable(); err != nil { + if err := s.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index c466c6ee2..bb4532ad2 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -50,7 +50,7 @@ func NewUninstall(cfg *Configuration) *Uninstall { // Run uninstalls the given release. func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) { - if err := u.cfg.KubeClient.IsReachable(); err != nil { + if err := u.cfg.GetKubeClient("").IsReachable(); err != nil { return nil, err } @@ -197,12 +197,14 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) { builder.WriteString("\n---\n" + file.Content) } - resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) + client := u.cfg.GetKubeClient(rel.Namespace) + + resources, err := client.Build(strings.NewReader(builder.String()), false) if err != nil { return "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} } if len(resources) > 0 { - _, errs = u.cfg.KubeClient.Delete(resources) + _, errs = client.Delete(resources) } return kept, errs } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index c439af79d..4fc43005a 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -40,7 +40,8 @@ import ( // // It provides the implementation of 'helm upgrade'. type Upgrade struct { - cfg *Configuration + cfg *Configuration + client kube.Interface ChartPathOptions @@ -107,7 +108,9 @@ func NewUpgrade(cfg *Configuration) *Upgrade { // Run executes the upgrade on the given release. func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - if err := u.cfg.KubeClient.IsReachable(); err != nil { + u.client = u.cfg.GetKubeClient(u.Namespace) + + if err := u.client.IsReachable(); err != nil { return nil, err } @@ -235,12 +238,12 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin if len(notesTxt) > 0 { upgradedRelease.Info.Notes = notesTxt } - err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) + err = validateManifest(u.client, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) return currentRelease, upgradedRelease, err } func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { - current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) + current, err := u.client.Build(bytes.NewBufferString(originalRelease.Manifest), false) if err != nil { // Checking for removed Kubernetes API error so can provide a more informative error message to the user // Ref: https://github.com/helm/helm/issues/7219 @@ -251,7 +254,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea } return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } - target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) + target, err := u.client.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) if err != nil { return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } @@ -312,7 +315,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) } - results, err := u.cfg.KubeClient.Update(current, target, u.Force) + results, err := u.client.Update(current, target, u.Force) if err != nil { u.cfg.recordRelease(originalRelease) return u.failRelease(upgradedRelease, results.Created, err) @@ -329,7 +332,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea } if u.Wait { - if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { + if err := u.client.Wait(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) return u.failRelease(upgradedRelease, results.Created, err) } @@ -364,7 +367,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e u.cfg.recordRelease(rel) if u.CleanupOnFail && len(created) > 0 { u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) - _, errs := u.cfg.KubeClient.Delete(created) + _, errs := u.client.Delete(created) if errs != nil { var errorList []string for _, e := range errs { diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index f16de6479..741d16e40 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -48,9 +48,9 @@ func TestUpgradeRelease_Wait(t *testing.T) { rel.Info.Status = release.StatusDeployed upAction.cfg.Releases.Create(rel) - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := upAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") - upAction.cfg.KubeClient = failer + // upAction.cfg.KubeClient = failer upAction.Wait = true vals := map[string]interface{}{} @@ -70,10 +70,10 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { rel.Info.Status = release.StatusDeployed upAction.cfg.Releases.Create(rel) - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := upAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") failer.DeleteError = fmt.Errorf("I tried to delete nil") - upAction.cfg.KubeClient = failer + // upAction.cfg.KubeClient = failer upAction.Wait = true upAction.CleanupOnFail = true vals := map[string]interface{}{} @@ -97,10 +97,10 @@ func TestUpgradeRelease_Atomic(t *testing.T) { rel.Info.Status = release.StatusDeployed upAction.cfg.Releases.Create(rel) - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := upAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) // We can't make Update error because then the rollback won't work failer.WatchUntilReadyError = fmt.Errorf("arming key removed") - upAction.cfg.KubeClient = failer + // upAction.cfg.KubeClient = failer upAction.Atomic = true vals := map[string]interface{}{} @@ -123,9 +123,9 @@ func TestUpgradeRelease_Atomic(t *testing.T) { rel.Info.Status = release.StatusDeployed upAction.cfg.Releases.Create(rel) - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + failer := upAction.cfg.GetKubeClient("").(*kubefake.FailingKubeClient) failer.UpdateError = fmt.Errorf("update fail") - upAction.cfg.KubeClient = failer + // upAction.cfg.KubeClient = failer upAction.Atomic = true vals := map[string]interface{}{}