diff --git a/pkg/action/action.go b/pkg/action/action.go index 1ca6a4dfa..ea2dc0dd7 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -375,10 +375,7 @@ func (cfg *Configuration) recordRelease(r *release.Release) { // Init initializes the action configuration func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { - kc, err := kube.New(getter) - if err != nil { - return err - } + kc := kube.New(getter) kc.Log = log lazyClient := &lazyClient{ diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 6637891c5..9d0bb390b 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -35,7 +35,7 @@ import ( ) // execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration) error { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -59,7 +59,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} } - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { return err } @@ -87,8 +87,12 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) } + waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + if err != nil { + return errors.Wrapf(err, "unable to get waiter") + } // Watch hook resources until they have completed - err = cfg.KubeClient.WatchUntilReady(resources, timeout) + err = waiter.WatchUntilReady(resources, timeout) // Note the time of success/failure h.LastRun.CompletedAt = helmtime.Now() // Mark hook as succeeded or failed @@ -101,7 +105,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, } // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook - if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); errDeleting != nil { + if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { // We log the error here as we want to propagate the hook failure upwards to the release object. log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) } @@ -118,7 +122,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // We log here as we still want to attempt hook resource deletion even if output logging fails. log.Printf("error outputting logs for hook failure: %v", err) } - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { return err } } @@ -139,7 +143,7 @@ func (x hookByWeight) Less(i, j int) bool { } // deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, timeout time.Duration) error { +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error { // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -155,7 +159,11 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return errors.New(joinErrors(errs)) } - if err := cfg.KubeClient.WaitForDelete(resources, timeout); err != nil { + waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + if err != nil { + return err + } + if err := waiter.WaitForDelete(resources, timeout); err != nil { return err } } diff --git a/pkg/action/install.go b/pkg/action/install.go index be76a634f..735b8ac17 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -79,7 +79,7 @@ type Install struct { HideSecret bool DisableHooks bool Replace bool - Wait kube.WaitStrategy + WaitStrategy kube.WaitStrategy WaitForJobs bool Devel bool DependencyUpdate bool @@ -180,8 +180,12 @@ func (i *Install) installCRDs(crds []chart.CRD) error { totalItems = append(totalItems, res...) } if len(totalItems) > 0 { + waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + if err != nil { + return errors.Wrapf(err, "unable to get waiter") + } // Give time for the CRD to be recognized. - if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { + if err := waiter.Wait(totalItems, 60*time.Second); err != nil { return err } @@ -289,11 +293,8 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both - if i.Wait == kube.HookOnlyStrategy && i.Atomic { - i.Wait = kube.StatusWatcherStrategy - } - if err := i.cfg.KubeClient.SetWaiter(i.Wait); err != nil { - return nil, fmt.Errorf("failed to set kube client waiter: %w", err) + if i.WaitStrategy == kube.HookOnlyStrategy && i.Atomic { + i.WaitStrategy = kube.StatusWatcherStrategy } caps, err := i.cfg.getCapabilities() @@ -453,7 +454,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource var err error // pre-install hooks if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout); err != nil { return rel, fmt.Errorf("failed pre-install: %s", err) } } @@ -470,17 +471,22 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource return rel, err } + waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + if err != nil { + return rel, fmt.Errorf("failed to get waiter: %w", err) + } + if i.WaitForJobs { - err = i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout) + err = waiter.WaitWithJobs(resources, i.Timeout) } else { - err = i.cfg.KubeClient.Wait(resources, i.Timeout) + err = waiter.Wait(resources, i.Timeout) } if err != nil { return rel, err } if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout); err != nil { return rel, fmt.Errorf("failed post-install: %s", err) } } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 331a2f71b..aafda86c2 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -412,7 +412,7 @@ func TestInstallRelease_Wait(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer - instAction.Wait = kube.StatusWatcherStrategy + instAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} goroutines := runtime.NumGoroutine() @@ -431,7 +431,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer - instAction.Wait = kube.StatusWatcherStrategy + instAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx, cancel := context.WithCancel(context.Background()) @@ -454,7 +454,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer - instAction.Wait = kube.StatusWatcherStrategy + instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitForJobs = true vals := map[string]interface{}{} diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index c6374523e..7edc3ed34 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -28,6 +28,7 @@ import ( v1 "k8s.io/api/core/v1" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -96,7 +97,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { rel.Hooks = executingHooks } - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout); err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(rel) return rel, err diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index a96a706e3..870f1e635 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -38,7 +38,7 @@ type Rollback struct { Version int Timeout time.Duration - Wait kube.WaitStrategy + WaitStrategy kube.WaitStrategy WaitForJobs bool DisableHooks bool DryRun bool @@ -61,10 +61,6 @@ func (r *Rollback) Run(name string) error { return err } - if err := r.cfg.KubeClient.SetWaiter(r.Wait); err != nil { - return fmt.Errorf("failed to set kube client waiter: %w", err) - } - r.cfg.Releases.MaxHistory = r.MaxHistory r.cfg.Log("preparing rollback of %s", name) @@ -181,7 +177,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // pre-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout); err != nil { return targetRelease, err } } else { @@ -227,16 +223,19 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.Log(err.Error()) } } - + waiter, err := r.cfg.KubeClient.GetWaiter(r.WaitStrategy) + if err != nil { + return nil, errors.Wrap(err, "unable to set metadata visitor from target release") + } if r.WaitForJobs { - if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { + if err := waiter.WaitWithJobs(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) return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) } } else { - if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { + if err := waiter.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) @@ -246,7 +245,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // post-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout); err != nil { return targetRelease, err } } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 503be0da5..eeff997d3 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -17,7 +17,6 @@ limitations under the License. package action import ( - "fmt" "strings" "time" @@ -42,7 +41,7 @@ type Uninstall struct { DryRun bool IgnoreNotFound bool KeepHistory bool - Wait kube.WaitStrategy + WaitStrategy kube.WaitStrategy DeletionPropagation string Timeout time.Duration Description string @@ -61,8 +60,9 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return nil, err } - if err := u.cfg.KubeClient.SetWaiter(u.Wait); err != nil { - return nil, fmt.Errorf("failed to set kube client waiter: %w", err) + waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + if err != nil { + return nil, err } if u.DryRun { @@ -111,7 +111,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) res := &release.UninstallReleaseResponse{Release: rel} if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout); err != nil { return res, err } } else { @@ -135,12 +135,12 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } res.Info = kept - if err := u.cfg.KubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil { + if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil { errs = append(errs, err) } if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout); err != nil { errs = append(errs, err) } } diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 5597abcdf..a83e4bc75 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -83,7 +83,7 @@ func TestUninstallRelease_Wait(t *testing.T) { unAction := uninstallAction(t) unAction.DisableHooks = true unAction.DryRun = false - unAction.Wait = kube.StatusWatcherStrategy + unAction.WaitStrategy = kube.StatusWatcherStrategy rel := releaseStub() rel.Name = "come-fail-away" @@ -114,7 +114,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { unAction := uninstallAction(t) unAction.DisableHooks = true unAction.DryRun = false - unAction.Wait = kube.HookOnlyStrategy + unAction.WaitStrategy = kube.HookOnlyStrategy unAction.DeletionPropagation = "foreground" rel := releaseStub() diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index ba5dfb5d1..e3b775a25 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -64,8 +64,8 @@ type Upgrade struct { SkipCRDs bool // Timeout is the timeout for this operation Timeout time.Duration - // Wait determines whether the wait operation should be performed and what type of wait. - Wait kube.WaitStrategy + // WaitStrategy determines what type of waiting should be done + WaitStrategy kube.WaitStrategy // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. WaitForJobs bool // DisableHooks disables hook processing if set to true. @@ -155,11 +155,8 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both - if u.Wait == kube.HookOnlyStrategy && u.Atomic { - u.Wait = kube.StatusWatcherStrategy - } - if err := u.cfg.KubeClient.SetWaiter(u.Wait); err != nil { - return nil, fmt.Errorf("failed to set kube client waiter: %w", err) + if u.WaitStrategy == kube.HookOnlyStrategy && u.Atomic { + u.WaitStrategy = kube.StatusWatcherStrategy } if err := chartutil.ValidateReleaseName(name); err != nil { @@ -423,7 +420,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // pre-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return } @@ -447,15 +444,20 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele u.cfg.Log(err.Error()) } } - + waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + if err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } if u.WaitForJobs { - if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { + if err := waiter.WaitWithJobs(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) return } } else { - if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { + if err := waiter.Wait(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) return @@ -464,7 +466,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // post-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) return } @@ -526,13 +528,8 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e rollin := NewRollback(u.cfg) rollin.Version = filteredHistory[0].Version - if u.Wait == kube.HookOnlyStrategy { - rollin.Wait = kube.StatusWatcherStrategy - } - // TODO pretty sure this is unnecessary as the waiter is already set if atomic at the start of upgrade - werr := u.cfg.KubeClient.SetWaiter(u.Wait) - if werr != nil { - return rel, errors.Wrapf(herr, "an error occurred while creating the waiter. original upgrade error: %s", err) + if u.WaitStrategy == kube.HookOnlyStrategy { + rollin.WaitStrategy = kube.StatusWatcherStrategy } rollin.WaitForJobs = u.WaitForJobs rollin.DisableHooks = u.DisableHooks diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index a36b7a3de..19869f6d6 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -53,7 +53,7 @@ func TestUpgradeRelease_Success(t *testing.T) { rel.Info.Status = release.StatusDeployed req.NoError(upAction.cfg.Releases.Create(rel)) - upAction.Wait = kube.StatusWatcherStrategy + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx, done := context.WithCancel(context.Background()) @@ -83,7 +83,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer - upAction.Wait = kube.StatusWatcherStrategy + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} res, err := upAction.Run(rel.Name, buildChart(), vals) @@ -105,7 +105,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer - upAction.Wait = kube.StatusWatcherStrategy + upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitForJobs = true vals := map[string]interface{}{} @@ -129,7 +129,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") failer.DeleteError = fmt.Errorf("I tried to delete nil") upAction.cfg.KubeClient = failer - upAction.Wait = kube.StatusWatcherStrategy + upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.CleanupOnFail = true vals := map[string]interface{}{} @@ -396,7 +396,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second upAction.cfg.KubeClient = failer - upAction.Wait = kube.StatusWatcherStrategy + upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]interface{}{} ctx := context.Background() diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 04055fde9..051612bb8 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -211,7 +211,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, install will ignore the check for helm annotations and take ownership of the existing resources") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) - AddWaitFlag(cmd, &client.Wait) + AddWaitFlag(cmd, &client.WaitStrategy) err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { requiredArgs := 2 diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 01a32b184..1823432dc 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -84,7 +84,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") - AddWaitFlag(cmd, &client.Wait) + AddWaitFlag(cmd, &client.WaitStrategy) return cmd } diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index 3a86cc598..4680c324a 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -79,7 +79,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.StringVar(&client.Description, "description", "", "add a custom description") - AddWaitFlag(cmd, &client.Wait) + AddWaitFlag(cmd, &client.WaitStrategy) return cmd } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 74d12ac40..afbbde435 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -136,7 +136,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.DisableHooks = client.DisableHooks instClient.SkipCRDs = client.SkipCRDs instClient.Timeout = client.Timeout - instClient.Wait = client.Wait + instClient.WaitStrategy = client.WaitStrategy instClient.WaitForJobs = client.WaitForJobs instClient.Devel = client.Devel instClient.Namespace = client.Namespace @@ -294,7 +294,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) - AddWaitFlag(cmd, &client.Wait) + AddWaitFlag(cmd, &client.WaitStrategy) err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 2 { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 61e681ad3..032f79850 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -125,7 +125,7 @@ func (c *Client) newStatusWatcher() (*statusWaiter, error) { }, nil } -func (c *Client) newWaiter(strategy WaitStrategy) (Waiter, error) { +func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { switch strategy { case LegacyStrategy: kc, err := c.Factory.KubernetesClientSet() @@ -148,7 +148,7 @@ func (c *Client) newWaiter(strategy WaitStrategy) (Waiter, error) { func (c *Client) SetWaiter(ws WaitStrategy) error { var err error - c.Waiter, err = c.newWaiter(ws) + c.Waiter, err = c.GetWaiter(ws) if err != nil { return err } @@ -156,7 +156,7 @@ func (c *Client) SetWaiter(ws WaitStrategy) error { } // New creates a new Client. -func New(getter genericclioptions.RESTClientGetter) (*Client, error) { +func New(getter genericclioptions.RESTClientGetter) *Client { if getter == nil { getter = genericclioptions.NewConfigFlags(true) } @@ -165,12 +165,7 @@ func New(getter genericclioptions.RESTClientGetter) (*Client, error) { Factory: factory, Log: nopLogger, } - var err error - c.Waiter, err = c.newWaiter(HookOnlyStrategy) - if err != nil { - return nil, err - } - return c, nil + return c } var nopLogger = func(_ string, _ ...interface{}) {} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 527f28a72..8ae1df238 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -516,7 +516,7 @@ func TestWait(t *testing.T) { }), } var err error - c.Waiter, err = c.newWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiter(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -573,7 +573,7 @@ func TestWaitJob(t *testing.T) { }), } var err error - c.Waiter, err = c.newWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiter(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -632,7 +632,7 @@ func TestWaitDelete(t *testing.T) { }), } var err error - c.Waiter, err = c.newWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiter(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -662,10 +662,7 @@ func TestWaitDelete(t *testing.T) { func TestReal(t *testing.T) { t.Skip("This is a live test, comment this line to run") - c, err := New(nil) - if err != nil { - t.Fatal(err) - } + c := New(nil) resources, err := c.Build(strings.NewReader(guestbookManifest), false) if err != nil { t.Fatal(err) @@ -675,10 +672,7 @@ func TestReal(t *testing.T) { } testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest - c, err = New(nil) - if err != nil { - t.Fatal(err) - } + c = New(nil) resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) if err != nil { t.Fatal(err) diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index c4322733a..f868afa1a 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -35,19 +35,29 @@ type FailingKubeClient struct { PrintingKubeClient CreateError error GetError error - WaitError error - WaitForDeleteError error DeleteError error DeleteWithPropagationError error - WatchUntilReadyError error UpdateError error BuildError error BuildTableError error BuildDummy bool BuildUnstructuredError error + WaitError error + WaitForDeleteError error + WatchUntilReadyError error WaitDuration time.Duration } +// FailingKubeWaiter implements kube.Waiter for testing purposes. +// It also has additional errors you can set to fail different functions, otherwise it delegates all its calls to `PrintingKubeWaiter` +type FailingKubeWaiter struct { + *PrintingKubeWaiter + waitError error + waitForDeleteError error + watchUntilReadyError error + waitDuration time.Duration +} + // Create returns the configured error if set or prints func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, error) { if f.CreateError != nil { @@ -65,28 +75,28 @@ func (f *FailingKubeClient) Get(resources kube.ResourceList, related bool) (map[ } // Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints. -func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error { - time.Sleep(f.WaitDuration) - if f.WaitError != nil { - return f.WaitError +func (f *FailingKubeWaiter) Wait(resources kube.ResourceList, d time.Duration) error { + time.Sleep(f.waitDuration) + if f.waitError != nil { + return f.waitError } - return f.PrintingKubeClient.Wait(resources, d) + return f.PrintingKubeWaiter.Wait(resources, d) } // WaitWithJobs returns the configured error if set or prints -func (f *FailingKubeClient) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { - if f.WaitError != nil { - return f.WaitError +func (f *FailingKubeWaiter) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { + if f.waitError != nil { + return f.waitError } - return f.PrintingKubeClient.WaitWithJobs(resources, d) + return f.PrintingKubeWaiter.WaitWithJobs(resources, d) } // WaitForDelete returns the configured error if set or prints -func (f *FailingKubeClient) WaitForDelete(resources kube.ResourceList, d time.Duration) error { - if f.WaitForDeleteError != nil { - return f.WaitForDeleteError +func (f *FailingKubeWaiter) WaitForDelete(resources kube.ResourceList, d time.Duration) error { + if f.waitForDeleteError != nil { + return f.waitForDeleteError } - return f.PrintingKubeClient.WaitForDelete(resources, d) + return f.PrintingKubeWaiter.WaitForDelete(resources, d) } // Delete returns the configured error if set or prints @@ -98,11 +108,11 @@ func (f *FailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, [ } // WatchUntilReady returns the configured error if set or prints -func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { - if f.WatchUntilReadyError != nil { - return f.WatchUntilReadyError +func (f *FailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { + if f.watchUntilReadyError != nil { + return f.watchUntilReadyError } - return f.PrintingKubeClient.WatchUntilReady(resources, d) + return f.PrintingKubeWaiter.WatchUntilReady(resources, d) } // Update returns the configured error if set or prints @@ -140,8 +150,16 @@ func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceL return f.PrintingKubeClient.DeleteWithPropagationPolicy(resources, policy) } -func (f *FailingKubeClient) SetWaiter(_ kube.WaitStrategy) error { - return nil +func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + waiter, _ := f.PrintingKubeClient.GetWaiter(ws) + printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) + return &FailingKubeWaiter{ + PrintingKubeWaiter: printingKubeWaiter, + waitError: f.WaitError, + waitForDeleteError: f.WaitForDeleteError, + watchUntilReadyError: f.WatchUntilReadyError, + waitDuration: f.WaitDuration, + }, nil } func createDummyResourceList() kube.ResourceList { @@ -151,5 +169,4 @@ func createDummyResourceList() kube.ResourceList { var resourceList kube.ResourceList resourceList.Append(&resInfo) return resourceList - } diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index fa25a04b3..f6659a904 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -37,6 +37,12 @@ type PrintingKubeClient struct { LogOutput io.Writer } +// PrintingKubeWaiter implements kube.Waiter, but simply prints the reader to the given output +type PrintingKubeWaiter struct { + Out io.Writer + LogOutput io.Writer +} + // IsReachable checks if the cluster is reachable func (p *PrintingKubeClient) IsReachable() error { return nil @@ -59,17 +65,23 @@ func (p *PrintingKubeClient) Get(resources kube.ResourceList, _ bool) (map[strin return make(map[string][]runtime.Object), nil } -func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error { +func (p *PrintingKubeWaiter) Wait(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } -func (p *PrintingKubeClient) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { +func (p *PrintingKubeWaiter) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } -func (p *PrintingKubeClient) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { +func (p *PrintingKubeWaiter) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +// WatchUntilReady implements KubeClient WatchUntilReady. +func (p *PrintingKubeWaiter) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { _, err := io.Copy(p.Out, bufferize(resources)) return err } @@ -85,12 +97,6 @@ func (p *PrintingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, return &kube.Result{Deleted: resources}, nil } -// WatchUntilReady implements KubeClient WatchUntilReady. -func (p *PrintingKubeClient) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - // Update implements KubeClient Update. func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kube.Result, error) { _, err := io.Copy(p.Out, bufferize(modified)) @@ -140,8 +146,8 @@ func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.Resource return &kube.Result{Deleted: resources}, nil } -func (p *PrintingKubeClient) SetWaiter(_ kube.WaitStrategy) error { - return nil +func (p *PrintingKubeClient) GetWaiter(_ kube.WaitStrategy) (kube.Waiter, error) { + return &PrintingKubeWaiter{Out: p.Out, LogOutput: p.LogOutput}, nil } func bufferize(resources kube.ResourceList) io.Reader { diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index d6ac823f1..fb42fed06 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -48,9 +48,9 @@ type Interface interface { Build(reader io.Reader, validate bool) (ResourceList, error) // IsReachable checks whether the client is able to connect to the cluster. IsReachable() error - // Set Waiter sets the Kube.Waiter - SetWaiter(ws WaitStrategy) error - Waiter + + // Get Waiter gets the Kube.Waiter + GetWaiter(ws WaitStrategy) (Waiter, error) } // Waiter defines methods related to waiting for resource states.