pull/32549/merge
Karthik Chowdary 16 hours ago committed by GitHub
commit a5fd7670b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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
@ -182,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{}
@ -543,6 +550,10 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted, resources ku
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 {

@ -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())
}

@ -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
@ -160,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()
@ -489,6 +496,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)

@ -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) {

@ -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
configureWaitProgress(client, out, outfmt)
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return fmt.Errorf("INSTALLATION FAILED: %w", err)
@ -370,6 +373,29 @@ 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) {
client.SetWaitProgress(nil)
if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone {
return
}
if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure {
return
}
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) {
if strategy == kube.HookOnlyStrategy && rollbackOnFailure {
strategy = kube.StatusWatcherStrategy
}
if outfmt != output.Table || strategy == kube.HookOnlyStrategy || dryRun != action.DryRunNone {
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

@ -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")
})
}
}

@ -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

@ -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

@ -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

@ -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

@ -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,19 @@ 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) {
client.SetWaitProgress(nil)
if outfmt != output.Table || client.DryRunStrategy != action.DryRunNone {
return
}
if client.WaitStrategy == kube.HookOnlyStrategy && !client.RollbackOnFailure {
return
}
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 {
client := action.NewUpgrade(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
@ -163,6 +177,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Replace = true
}
configureWaitProgress(instClient, out, outfmt)
rel, err := runInstall(args, instClient, valueOpts, out)
if err != nil {
return err
@ -257,6 +273,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cancel()
}()
configureUpgradeWaitProgress(client, out, outfmt)
rel, err := client.RunWithContext(ctx, args[0], ch, vals)
if err != nil {
return fmt.Errorf("UPGRADE FAILED: %w", err)

@ -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")
})
}
}

Loading…
Cancel
Save