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] 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 {