From a2504521abe3f02caa137d163280271dd14f6d5b Mon Sep 17 00:00:00 2001 From: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> Date: Mon, 17 Aug 2026 07:02:12 +0000 Subject: [PATCH 1/5] feat(cmd): show progress before waiting for resources Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> --- pkg/cmd/install.go | 10 ++++++++++ pkg/cmd/testdata/output/install-with-wait-for-jobs.txt | 1 + pkg/cmd/testdata/output/install-with-wait.txt | 1 + pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt | 1 + pkg/cmd/testdata/output/upgrade-with-wait.txt | 1 + pkg/cmd/upgrade.go | 4 ++++ 6 files changed, 18 insertions(+) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index b6cee5843..b9affaaaa 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -38,6 +38,7 @@ import ( "helm.sh/helm/v4/pkg/cmd/require" "helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/kube" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -157,6 +158,8 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.DryRunStrategy = dryRunStrategy + printWaitMessage(out, outfmt, client.WaitStrategy, client.Timeout) + rel, err := runInstall(args, client, valueOpts, out) if err != nil { return fmt.Errorf("INSTALLATION FAILED: %w", err) @@ -370,6 +373,13 @@ func checkIfInstallable(ch chart.Accessor) error { return fmt.Errorf("%s charts are not installable", meta["Type"]) } +func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, timeout time.Duration) { + if outfmt != output.Table || strategy == kube.HookOnlyStrategy { + return + } + fmt.Fprintf(out, "Waiting for resources to become ready (timeout: %s)\n", timeout) +} + // Provide dynamic auto-completion for the install and template commands func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) { requiredArgs := 1 diff --git a/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt b/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt index c5676c610..67e223847 100644 --- a/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt +++ b/pkg/cmd/testdata/output/install-with-wait-for-jobs.txt @@ -1,3 +1,4 @@ +Waiting for resources to become ready (timeout: 5m0s) NAME: apollo LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default diff --git a/pkg/cmd/testdata/output/install-with-wait.txt b/pkg/cmd/testdata/output/install-with-wait.txt index c5676c610..67e223847 100644 --- a/pkg/cmd/testdata/output/install-with-wait.txt +++ b/pkg/cmd/testdata/output/install-with-wait.txt @@ -1,3 +1,4 @@ +Waiting for resources to become ready (timeout: 5m0s) NAME: apollo LAST DEPLOYED: Fri Sep 2 22:04:05 1977 NAMESPACE: default diff --git a/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt b/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt index 21784413c..44524d8df 100644 --- a/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt +++ b/pkg/cmd/testdata/output/upgrade-with-wait-for-jobs.txt @@ -1,3 +1,4 @@ +Waiting for resources to become ready (timeout: 5m0s) Release "crazy-bunny" has been upgraded. Happy Helming! NAME: crazy-bunny LAST DEPLOYED: Fri Sep 2 22:04:05 1977 diff --git a/pkg/cmd/testdata/output/upgrade-with-wait.txt b/pkg/cmd/testdata/output/upgrade-with-wait.txt index 21784413c..44524d8df 100644 --- a/pkg/cmd/testdata/output/upgrade-with-wait.txt +++ b/pkg/cmd/testdata/output/upgrade-with-wait.txt @@ -1,3 +1,4 @@ +Waiting for resources to become ready (timeout: 5m0s) Release "crazy-bunny" has been upgraded. Happy Helming! NAME: crazy-bunny LAST DEPLOYED: Fri Sep 2 22:04:05 1977 diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 688842141..3df130b3c 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -163,6 +163,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Replace = true } + printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.Timeout) + rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { return err @@ -257,6 +259,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cancel() }() + printWaitMessage(out, outfmt, client.WaitStrategy, client.Timeout) + rel, err := client.RunWithContext(ctx, args[0], ch, vals) if err != nil { return fmt.Errorf("UPGRADE FAILED: %w", err) From f590c59930ee922521db0168d40ae1dbb67920ab Mon Sep 17 00:00:00 2001 From: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:22:13 +0000 Subject: [PATCH 2/5] fix(cmd): suppress wait progress during dry runs Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> --- pkg/cmd/install.go | 6 +++--- pkg/cmd/upgrade.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index b9affaaaa..ff8a3de08 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -158,7 +158,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.DryRunStrategy = dryRunStrategy - printWaitMessage(out, outfmt, client.WaitStrategy, client.Timeout) + printWaitMessage(out, outfmt, client.WaitStrategy, client.DryRunStrategy, client.Timeout) rel, err := runInstall(args, client, valueOpts, out) if err != nil { @@ -373,8 +373,8 @@ func checkIfInstallable(ch chart.Accessor) error { return fmt.Errorf("%s charts are not installable", meta["Type"]) } -func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, timeout time.Duration) { - if outfmt != output.Table || strategy == kube.HookOnlyStrategy { +func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, dryRun action.DryRunStrategy, timeout time.Duration) { + if outfmt != output.Table || strategy == kube.HookOnlyStrategy || dryRun != action.DryRunNone { return } fmt.Fprintf(out, "Waiting for resources to become ready (timeout: %s)\n", timeout) diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 3df130b3c..e8bc834ac 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -163,7 +163,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Replace = true } - printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.Timeout) + printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.DryRunStrategy, instClient.Timeout) rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { @@ -259,7 +259,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cancel() }() - printWaitMessage(out, outfmt, client.WaitStrategy, client.Timeout) + printWaitMessage(out, outfmt, client.WaitStrategy, client.DryRunStrategy, client.Timeout) rel, err := client.RunWithContext(ctx, args[0], ch, vals) if err != nil { From 1c632eee59b34390fed387b94ad39b096bbd32ff Mon Sep 17 00:00:00 2001 From: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> Date: Thu, 27 Aug 2026 06:51:21 +0000 Subject: [PATCH 3/5] fix(cmd): report implicit watcher waits Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> --- pkg/cmd/install.go | 9 +++++-- pkg/cmd/install_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ pkg/cmd/upgrade.go | 4 +-- pkg/cmd/upgrade_test.go | 31 ++++++++++++++++++++++ 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index ff8a3de08..727611cad 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -158,7 +158,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.DryRunStrategy = dryRunStrategy - printWaitMessage(out, outfmt, client.WaitStrategy, client.DryRunStrategy, client.Timeout) + printWaitMessage(out, outfmt, client.WaitStrategy, client.RollbackOnFailure, client.DryRunStrategy, client.Timeout) rel, err := runInstall(args, client, valueOpts, out) if err != nil { @@ -373,7 +373,12 @@ func checkIfInstallable(ch chart.Accessor) error { return fmt.Errorf("%s charts are not installable", meta["Type"]) } -func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, dryRun action.DryRunStrategy, timeout time.Duration) { +func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, rollbackOnFailure bool, dryRun action.DryRunStrategy, timeout time.Duration) { + // Rollback-on-failure implicitly enables watcher waits in the action layer. + // Account for that here, before the action mutates its WaitStrategy. + if strategy == kube.HookOnlyStrategy && rollbackOnFailure { + strategy = kube.StatusWatcherStrategy + } if outfmt != output.Table || strategy == kube.HookOnlyStrategy || dryRun != action.DryRunNone { return } diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index d1e081e5c..59092efa9 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -17,14 +17,19 @@ limitations under the License. package cmd import ( + "bytes" "fmt" "net/http" "net/http/httptest" "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli/output" + "helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/repo/v1/repotest" ) @@ -323,3 +328,56 @@ func TestInstallFileCompletion(t *testing.T) { checkFileCompletion(t, "install myname", true) checkFileCompletion(t, "install myname mychart", false) } + +func TestPrintWaitMessage(t *testing.T) { + const message = "Waiting for resources to become ready (timeout: 42s)\n" + tests := []struct { + name string + format output.Format + strategy kube.WaitStrategy + rollbackOnFailure bool + dryRun action.DryRunStrategy + want string + }{ + {name: "explicit watcher wait", format: output.Table, strategy: kube.StatusWatcherStrategy, dryRun: action.DryRunNone, want: message}, + {name: "implicit watcher wait", format: output.Table, strategy: kube.HookOnlyStrategy, rollbackOnFailure: true, dryRun: action.DryRunNone, want: message}, + {name: "hook-only wait", format: output.Table, strategy: kube.HookOnlyStrategy, dryRun: action.DryRunNone}, + {name: "structured output", format: output.JSON, strategy: kube.StatusWatcherStrategy, dryRun: action.DryRunNone}, + {name: "client dry run", format: output.Table, strategy: kube.StatusWatcherStrategy, dryRun: action.DryRunClient}, + {name: "server dry run", format: output.Table, strategy: kube.StatusWatcherStrategy, dryRun: action.DryRunServer}, + {name: "implicit wait client dry run", format: output.Table, strategy: kube.HookOnlyStrategy, rollbackOnFailure: true, dryRun: action.DryRunClient}, + {name: "implicit wait server dry run", format: output.Table, strategy: kube.HookOnlyStrategy, rollbackOnFailure: true, dryRun: action.DryRunServer}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var out bytes.Buffer + printWaitMessage(&out, tt.format, tt.strategy, tt.rollbackOnFailure, tt.dryRun, 42*time.Second) + require.Equal(t, tt.want, out.String()) + }) + } +} + +func TestInstallImplicitWaitProgress(t *testing.T) { + for _, flag := range []string{"--rollback-on-failure", "--atomic"} { + t.Run(flag, func(t *testing.T) { + defer resetEnv()() + + _, out, err := executeActionCommand("install implicit-wait testdata/testcharts/empty " + flag) + require.NoError(t, err) + require.Contains(t, out, "Waiting for resources to become ready (timeout: 5m0s)\n") + }) + } +} + +func TestInstallDryRunSuppressesWaitProgress(t *testing.T) { + for _, dryRun := range []string{"client", "server"} { + t.Run(dryRun, func(t *testing.T) { + defer resetEnv()() + + _, out, err := executeActionCommand("install dry-run-wait testdata/testcharts/empty --rollback-on-failure --dry-run=" + dryRun) + require.NoError(t, err) + require.NotContains(t, out, "Waiting for resources to become ready") + }) + } +} diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index e8bc834ac..6be0bab45 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -163,7 +163,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Replace = true } - printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.DryRunStrategy, instClient.Timeout) + printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.RollbackOnFailure, instClient.DryRunStrategy, instClient.Timeout) rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { @@ -259,7 +259,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cancel() }() - printWaitMessage(out, outfmt, client.WaitStrategy, client.DryRunStrategy, client.Timeout) + printWaitMessage(out, outfmt, client.WaitStrategy, client.RollbackOnFailure, client.DryRunStrategy, client.Timeout) rel, err := client.RunWithContext(ctx, args[0], ch, vals) if err != nil { diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 5aba7b11b..3a16d8323 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -587,3 +587,34 @@ func TestUpgradeInstallServerSideApply(t *testing.T) { }) } } + +func TestUpgradeImplicitWaitProgress(t *testing.T) { + tests := []struct { + name string + install bool + }{ + {name: "upgrade"}, + {name: "upgrade install", install: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer resetEnv()() + + releaseName := "implicit-wait" + relMock, ch, chartPath := prepareMockRelease(t, releaseName) + store := storageFixture() + installFlag := "" + if tt.install { + installFlag = "--install" + } else { + require.NoError(t, store.Create(relMock(releaseName, 1, ch))) + } + + cmd := fmt.Sprintf("upgrade %s %s --rollback-on-failure '%s'", releaseName, installFlag, chartPath) + _, out, err := executeActionCommandC(store, cmd) + require.NoError(t, err) + assert.Contains(t, out, "Waiting for resources to become ready (timeout: 5m0s)\n") + }) + } +} From 6a1a3d092f2b1de12cc1c7222759526ee7c1427c Mon Sep 17 00:00:00 2001 From: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> Date: Sat, 29 Aug 2026 09:09:02 +0000 Subject: [PATCH 4/5] fix(cmd): print progress at resource wait boundary Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> --- pkg/action/install.go | 18 ++++++++++++------ pkg/action/upgrade.go | 6 ++++++ pkg/cmd/install.go | 16 +++++++++++++--- pkg/cmd/upgrade.go | 17 +++++++++++++++-- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 605c423bc..a2b4369d6 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -100,12 +100,14 @@ type Install struct { Devel bool DependencyUpdate bool Timeout time.Duration - Namespace string - ReleaseName string - GenerateName bool - NameTemplate string - Description string - OutputDir string + // WaitProgress is called immediately before waiting for resources. + WaitProgress func(time.Duration) + Namespace string + ReleaseName string + GenerateName bool + NameTemplate string + Description string + OutputDir string // RollbackOnFailure enables rolling back (uninstalling) the release on failure if set RollbackOnFailure bool SkipCRDs bool @@ -543,6 +545,10 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource return rel, fmt.Errorf("failed to get waiter: %w", err) } + if i.WaitProgress != nil { + i.WaitProgress(i.Timeout) + } + if i.WaitForJobs { err = waiter.WaitWithJobs(resources, i.Timeout) } else { diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 7f66ceefb..5a45d4730 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -76,6 +76,8 @@ type Upgrade struct { WaitOptions []kube.WaitOption // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. WaitForJobs bool + // WaitProgress is called immediately before waiting for resources. + WaitProgress func(time.Duration) // DisableHooks disables hook processing if set to true. DisableHooks bool // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster @@ -489,6 +491,10 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) return } + if u.WaitProgress != nil { + u.WaitProgress(u.Timeout) + } + if u.WaitForJobs { if err := waiter.WaitWithJobs(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 727611cad..d31958a60 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -158,7 +158,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.DryRunStrategy = dryRunStrategy - printWaitMessage(out, outfmt, client.WaitStrategy, client.RollbackOnFailure, client.DryRunStrategy, client.Timeout) + configureWaitProgress(client, out, outfmt) rel, err := runInstall(args, client, valueOpts, out) if err != nil { @@ -373,9 +373,19 @@ func checkIfInstallable(ch chart.Accessor) error { return fmt.Errorf("%s charts are not installable", meta["Type"]) } +func configureWaitProgress(client *action.Install, out io.Writer, outfmt output.Format) { + if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone { + return + } + if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure { + return + } + client.WaitProgress = func(timeout time.Duration) { + printWaitMessage(out, outfmt, kube.StatusWatcherStrategy, false, action.DryRunNone, timeout) + } +} + func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, rollbackOnFailure bool, dryRun action.DryRunStrategy, timeout time.Duration) { - // Rollback-on-failure implicitly enables watcher waits in the action layer. - // Account for that here, before the action mutates its WaitStrategy. if strategy == kube.HookOnlyStrategy && rollbackOnFailure { strategy = kube.StatusWatcherStrategy } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 6be0bab45..acf247cbd 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -38,6 +38,7 @@ import ( "helm.sh/helm/v4/pkg/cmd/require" "helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/kube" ri "helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/storage/driver" @@ -82,6 +83,18 @@ which can contain sensitive values. To hide Kubernetes Secrets use the --hide-secret flag. Please carefully consider how and when these flags are used. ` +func configureUpgradeWaitProgress(client *action.Upgrade, out io.Writer, outfmt output.Format) { + if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone { + return + } + if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure { + return + } + client.WaitProgress = func(timeout time.Duration) { + printWaitMessage(out, outfmt, kube.StatusWatcherStrategy, false, action.DryRunNone, timeout) + } +} + func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewUpgrade(cfg) client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) @@ -163,7 +176,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Replace = true } - printWaitMessage(out, outfmt, instClient.WaitStrategy, instClient.RollbackOnFailure, instClient.DryRunStrategy, instClient.Timeout) + configureWaitProgress(instClient, out, outfmt) rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { @@ -259,7 +272,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cancel() }() - printWaitMessage(out, outfmt, client.WaitStrategy, client.RollbackOnFailure, client.DryRunStrategy, client.Timeout) + configureUpgradeWaitProgress(client, out, outfmt) rel, err := client.RunWithContext(ctx, args[0], ch, vals) if err != nil { From 6f1888b78395af3a42a6b15e47b4ea375e102d36 Mon Sep 17 00:00:00 2001 From: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:47:45 +0000 Subject: [PATCH 5/5] fix(action): keep wait progress callback private Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com> --- pkg/action/install.go | 13 +++++++++---- pkg/action/install_test.go | 6 ++++++ pkg/action/upgrade.go | 13 +++++++++---- pkg/action/upgrade_test.go | 6 ++++++ pkg/cmd/install.go | 5 +++-- pkg/cmd/upgrade.go | 5 +++-- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index a2b4369d6..098e7fe0a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -100,8 +100,8 @@ type Install struct { Devel bool DependencyUpdate bool Timeout time.Duration - // WaitProgress is called immediately before waiting for resources. - WaitProgress func(time.Duration) + // waitProgress is called immediately before waiting for resources. + waitProgress func(time.Duration) Namespace string ReleaseName string GenerateName bool @@ -184,6 +184,11 @@ func (i *Install) GetRegistryClient() *registry.Client { return i.registryClient } +// SetWaitProgress configures a callback invoked immediately before waiting for resources. +func (i *Install) SetWaitProgress(waitProgress func(time.Duration)) { + i.waitProgress = waitProgress +} + func (i *Install) installCRDs(crds []chart.CRD) error { // We do these one file at a time in the order they were read. totalItems := []*resource.Info{} @@ -545,8 +550,8 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource return rel, fmt.Errorf("failed to get waiter: %w", err) } - if i.WaitProgress != nil { - i.WaitProgress(i.Timeout) + if i.waitProgress != nil { + i.waitProgress(i.Timeout) } if i.WaitForJobs { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 2d83abe27..209678864 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -618,6 +618,11 @@ func TestInstallRelease_Wait(t *testing.T) { failer.WaitError = errors.New("I timed out") instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy + progressCalls := 0 + instAction.SetWaitProgress(func(timeout time.Duration) { + assert.Equal(t, instAction.Timeout, timeout) + progressCalls++ + }) vals := map[string]any{} goroutines := instAction.getGoroutineCount() @@ -628,6 +633,7 @@ func TestInstallRelease_Wait(t *testing.T) { req.NoError(err) is.Contains(res.Info.Description, "I timed out") is.Equal(rcommon.StatusFailed, res.Info.Status) + is.Equal(1, progressCalls) is.Equal(goroutines, instAction.getGoroutineCount()) } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 5a45d4730..30fa11103 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -76,8 +76,8 @@ type Upgrade struct { WaitOptions []kube.WaitOption // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. WaitForJobs bool - // WaitProgress is called immediately before waiting for resources. - WaitProgress func(time.Duration) + // waitProgress is called immediately before waiting for resources. + waitProgress func(time.Duration) // DisableHooks disables hook processing if set to true. DisableHooks bool // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster @@ -162,6 +162,11 @@ func (u *Upgrade) SetRegistryClient(client *registry.Client) { u.registryClient = client } +// SetWaitProgress configures a callback invoked immediately before waiting for resources. +func (u *Upgrade) SetWaitProgress(waitProgress func(time.Duration)) { + u.waitProgress = waitProgress +} + // Run executes the upgrade on the given release. func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]any) (ri.Releaser, error) { ctx := context.Background() @@ -491,8 +496,8 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) return } - if u.WaitProgress != nil { - u.WaitProgress(u.Timeout) + if u.waitProgress != nil { + u.waitProgress(u.Timeout) } if u.WaitForJobs { diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 53419b6a8..70d088bb9 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -94,6 +94,11 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer.WaitError = errors.New("I timed out") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy + progressCalls := 0 + upAction.SetWaitProgress(func(timeout time.Duration) { + assert.Equal(t, upAction.Timeout, timeout) + progressCalls++ + }) vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) @@ -102,6 +107,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { req.NoError(err) is.Contains(res.Info.Description, "I timed out") is.Equal(common.StatusFailed, res.Info.Status) + is.Equal(1, progressCalls) } func TestUpgradeRelease_WaitForJobs(t *testing.T) { diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index d31958a60..b44c7b72a 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -374,15 +374,16 @@ func checkIfInstallable(ch chart.Accessor) error { } func configureWaitProgress(client *action.Install, out io.Writer, outfmt output.Format) { + client.SetWaitProgress(nil) if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone { return } if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure { return } - client.WaitProgress = func(timeout time.Duration) { + client.SetWaitProgress(func(timeout time.Duration) { printWaitMessage(out, outfmt, kube.StatusWatcherStrategy, false, action.DryRunNone, timeout) - } + }) } func printWaitMessage(out io.Writer, outfmt output.Format, strategy kube.WaitStrategy, rollbackOnFailure bool, dryRun action.DryRunStrategy, timeout time.Duration) { diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index acf247cbd..28b514311 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -84,15 +84,16 @@ which can contain sensitive values. To hide Kubernetes Secrets use the ` func configureUpgradeWaitProgress(client *action.Upgrade, out io.Writer, outfmt output.Format) { + client.SetWaitProgress(nil) if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone { return } if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure { return } - client.WaitProgress = func(timeout time.Duration) { + client.SetWaitProgress(func(timeout time.Duration) { printWaitMessage(out, outfmt, kube.StatusWatcherStrategy, false, action.DryRunNone, timeout) - } + }) } func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {