feat(spec): Task 5 — OrderedWaitStrategy, --wait=ordered, --readiness-timeout

Adds OrderedWaitStrategy ("ordered") to the WaitStrategy enum in pkg/kube.
Updates --wait flag to accept "ordered" and flags.go validation.
Adds --readiness-timeout flag (default 1m) to install and upgrade commands.
Adds ReadinessTimeout field to Install and Upgrade action structs.
Validates ReadinessTimeout must not exceed Timeout at run time.
pull/31992/head
caretak3r 8 months ago
parent 56787a1bae
commit 1e41788adc

@ -100,6 +100,10 @@ type Install struct {
Devel bool
DependencyUpdate bool
Timeout time.Duration
// ReadinessTimeout is the per-batch timeout when --wait=ordered is used.
// Each batch waits at most this long for resources to become ready.
// Must not exceed Timeout. Defaults to 1 minute when zero.
ReadinessTimeout time.Duration
Namespace string
ReleaseName string
GenerateName bool
@ -290,6 +294,10 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
return nil, fmt.Errorf("release name check failed: %w", err)
}
if i.ReadinessTimeout > 0 && i.Timeout > 0 && i.ReadinessTimeout > i.Timeout {
return nil, fmt.Errorf("--readiness-timeout (%s) must not exceed --timeout (%s)", i.ReadinessTimeout, i.Timeout)
}
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
i.cfg.Logger().Error("chart dependencies processing failed", slog.Any("error", err))
return nil, fmt.Errorf("chart dependencies processing failed: %w", err)

@ -70,6 +70,10 @@ type Upgrade struct {
SkipCRDs bool
// Timeout is the timeout for this operation
Timeout time.Duration
// ReadinessTimeout is the per-batch timeout when --wait=ordered is used.
// Each batch waits at most this long for resources to become ready.
// Must not exceed Timeout. Defaults to 1 minute when zero.
ReadinessTimeout time.Duration
// WaitStrategy determines what type of waiting should be done
WaitStrategy kube.WaitStrategy
// WaitOptions are additional options for waiting on resources
@ -187,6 +191,10 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
return nil, fmt.Errorf("release name is invalid: %s", name)
}
if u.ReadinessTimeout > 0 && u.Timeout > 0 && u.ReadinessTimeout > u.Timeout {
return nil, fmt.Errorf("--readiness-timeout (%s) must not exceed --timeout (%s)", u.ReadinessTimeout, u.Timeout)
}
u.cfg.Logger().Debug("preparing upgrade", "name", name)
currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals)
if err != nil {

@ -59,7 +59,7 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) {
cmd.Flags().Var(
newWaitValue(kube.HookOnlyStrategy, wait),
"wait",
"wait until resources are ready (up to --timeout). Use '--wait' alone for 'watcher' strategy, or specify one of: 'watcher', 'hookOnly', 'legacy'. Default when flag is omitted: 'hookOnly'.",
"wait until resources are ready (up to --timeout). Use '--wait' alone for 'watcher' strategy, or specify one of: 'watcher', 'hookOnly', 'legacy', 'ordered'. Default when flag is omitted: 'hookOnly'.",
)
cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy)
}
@ -80,7 +80,7 @@ func (ws *waitValue) String() string {
func (ws *waitValue) Set(s string) error {
switch s {
case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy), string(kube.HookOnlyStrategy):
case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy), string(kube.HookOnlyStrategy), string(kube.OrderedWaitStrategy):
*ws = waitValue(s)
return nil
case "true":
@ -92,7 +92,7 @@ func (ws *waitValue) Set(s string) error {
*ws = waitValue(kube.HookOnlyStrategy)
return nil
default:
return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.HookOnlyStrategy, kube.LegacyStrategy)
return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.HookOnlyStrategy, kube.LegacyStrategy, kube.OrderedWaitStrategy)
}
}

@ -25,6 +25,7 @@ import (
"helm.sh/helm/v4/pkg/action"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -99,6 +100,27 @@ func outputFlagCompletionTest(t *testing.T, cmdName string) {
runTestCmd(t, tests)
}
func TestWaitValueOrdered(t *testing.T) {
// --wait=ordered should be accepted and set the strategy correctly.
var ws kube.WaitStrategy
wv := newWaitValue(kube.HookOnlyStrategy, &ws)
if err := wv.Set("ordered"); err != nil {
t.Fatalf("expected --wait=ordered to be accepted, got error: %v", err)
}
if ws != kube.OrderedWaitStrategy {
t.Errorf("expected OrderedWaitStrategy, got %q", ws)
}
}
func TestWaitValueInvalid(t *testing.T) {
var ws kube.WaitStrategy
wv := newWaitValue(kube.HookOnlyStrategy, &ws)
if err := wv.Set("invalid"); err == nil {
t.Error("expected error for invalid wait strategy, got nil")
}
}
func TestPostRendererFlagSetOnce(t *testing.T) {
cfg := action.Configuration{}
client := action.NewInstall(&cfg)

@ -212,6 +212,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
AddWaitFlag(cmd, &client.WaitStrategy)
f.DurationVar(&client.ReadinessTimeout, "readiness-timeout", time.Minute, "per-batch timeout when --wait=ordered is used; each resource batch must become ready within this duration (must not exceed --timeout)")
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")

@ -305,6 +305,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer, settings)
AddWaitFlag(cmd, &client.WaitStrategy)
f.DurationVar(&client.ReadinessTimeout, "readiness-timeout", time.Minute, "per-batch timeout when --wait=ordered is used; each resource batch must become ready within this duration (must not exceed --timeout)")
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")

@ -117,6 +117,13 @@ const (
// HookOnlyStrategy: wait only for hook Pods/Jobs to complete; does not wait for general chart resources.
HookOnlyStrategy WaitStrategy = "hookOnly"
// OrderedWaitStrategy: deploy resources in DAG-ordered batches, waiting for each batch to become ready
// before proceeding to the next. Subchart and resource-group sequencing is driven by
// helm.sh/depends-on/subcharts, helm.sh/resource-group, and helm.sh/depends-on/resource-groups annotations.
// Custom readiness conditions via helm.sh/readiness-success and helm.sh/readiness-failure are evaluated
// per-resource; otherwise kstatus is used.
OrderedWaitStrategy WaitStrategy = "ordered"
)
type FieldValidationDirective string
@ -200,10 +207,14 @@ func (c *Client) GetWaiterWithOptions(strategy WaitStrategy, opts ...WaitOption)
return nil, err
}
return &hookOnlyWaiter{sw: sw}, nil
case OrderedWaitStrategy:
// Sequencing logic lives in the action layer; at the kube level, ordered
// strategy uses the status watcher for per-batch readiness checks.
return c.newStatusWatcher(opts...)
case "":
return nil, errors.New("wait strategy not set. Choose one of: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy))
return nil, errors.New("wait strategy not set. Choose one of: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy) + ", " + string(OrderedWaitStrategy))
default:
return nil, errors.New("unknown wait strategy (s" + string(strategy) + "). Valid values are: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy))
return nil, errors.New("unknown wait strategy (" + string(strategy) + "). Valid values are: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy) + ", " + string(OrderedWaitStrategy))
}
}

Loading…
Cancel
Save