add hook only waiter

Signed-off-by: Austin Abro <AustinAbro321@gmail.com>
pull/13604/head
Austin Abro 7 months ago
parent 2b03c527f1
commit f2dd2c9109
No known key found for this signature in database
GPG Key ID: 92EB5159E403F9D6

@ -54,7 +54,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) {
cmd.Flags().Var( cmd.Flags().Var(
newWaitValue(wait), newWaitValue(kube.HookOnlyStrategy, wait),
"wait", "wait",
"if set, will wait until all resources are in the expected state before marking the operation as successful. It will wait for as long as --timeout. Options are (true, false, watcher, and legacy)", "if set, will wait until all resources are in the expected state before marking the operation as successful. It will wait for as long as --timeout. Options are (true, false, watcher, and legacy)",
) )
@ -64,7 +64,8 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) {
type waitValue kube.WaitStrategy type waitValue kube.WaitStrategy
func newWaitValue(ws *kube.WaitStrategy) *waitValue { func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue {
*ws = defaultValue
return (*waitValue)(ws) return (*waitValue)(ws)
} }
@ -77,7 +78,7 @@ func (ws *waitValue) String() string {
func (ws *waitValue) Set(s string) error { func (ws *waitValue) Set(s string) error {
switch s { switch s {
case string(kube.StatusWatcherStrategy), string(kube.LegacyWaiterStrategy): case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy):
*ws = waitValue(s) *ws = waitValue(s)
return nil return nil
case "true": case "true":
@ -87,7 +88,7 @@ func (ws *waitValue) Set(s string) error {
*ws = "" *ws = ""
return nil return nil
default: default:
return fmt.Errorf("invalid wait input %q. Valid inputs are true, false, %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyWaiterStrategy) return fmt.Errorf("invalid wait input %q. Valid inputs are true, false, %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyStrategy)
} }
} }

@ -198,7 +198,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically to \"watcher\" if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation")

@ -76,10 +76,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`) f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`)
f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.") 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.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") f.StringVar(&client.Description, "description", "", "add a custom description")
AddWaitFlag(cmd, &client.Wait)
return cmd return cmd
} }

@ -279,7 +279,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored")
f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored")
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.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.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically to \"watcher\" if --atomic is used")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")

@ -293,9 +293,9 @@ 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 // Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both // the user doesn't have to specify both
if !i.shouldWait() { if i.Wait == kube.HookOnlyStrategy {
if i.Atomic { if i.Atomic {
i.Wait = "watcher" i.Wait = kube.StatusWatcherStrategy
} }
} }
@ -473,15 +473,13 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
return rel, err return rel, err
} }
if i.shouldWait() { if i.WaitForJobs {
if i.WaitForJobs { err = i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout)
err = i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout) } else {
} else { err = i.cfg.KubeClient.Wait(resources, i.Timeout)
err = i.cfg.KubeClient.Wait(resources, i.Timeout) }
} if err != nil {
if err != nil { return rel, err
return rel, err
}
} }
if !i.DisableHooks { if !i.DisableHooks {

@ -228,21 +228,19 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
} }
} }
if r.shouldWait() { if r.WaitForJobs {
if r.WaitForJobs { if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil {
if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease)
r.cfg.recordRelease(targetRelease) return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) }
} } else {
} else { if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil {
if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease)
r.cfg.recordRelease(targetRelease) return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
}
} }
} }

@ -41,7 +41,7 @@ type Uninstall struct {
DryRun bool DryRun bool
IgnoreNotFound bool IgnoreNotFound bool
KeepHistory bool KeepHistory bool
Wait bool Wait kube.WaitStrategy
DeletionPropagation string DeletionPropagation string
Timeout time.Duration Timeout time.Duration
Description string Description string
@ -130,10 +130,8 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
} }
res.Info = kept res.Info = kept
if u.Wait { if err := u.cfg.KubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil {
if err := u.cfg.KubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil { errs = append(errs, err)
errs = append(errs, err)
}
} }
if !u.DisableHooks { if !u.DisableHooks {

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
@ -82,7 +83,7 @@ func TestUninstallRelease_Wait(t *testing.T) {
unAction := uninstallAction(t) unAction := uninstallAction(t)
unAction.DisableHooks = true unAction.DisableHooks = true
unAction.DryRun = false unAction.DryRun = false
unAction.Wait = true unAction.Wait = kube.StatusWatcherStrategy
rel := releaseStub() rel := releaseStub()
rel.Name = "come-fail-away" rel.Name = "come-fail-away"
@ -113,7 +114,7 @@ func TestUninstallRelease_Cascade(t *testing.T) {
unAction := uninstallAction(t) unAction := uninstallAction(t)
unAction.DisableHooks = true unAction.DisableHooks = true
unAction.DryRun = false unAction.DryRun = false
unAction.Wait = false unAction.Wait = kube.HookOnlyStrategy
unAction.DeletionPropagation = "foreground" unAction.DeletionPropagation = "foreground"
rel := releaseStub() rel := releaseStub()

@ -155,7 +155,7 @@ 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 // Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both // the user doesn't have to specify both
if !u.shouldWait() { if u.Wait == kube.HookOnlyStrategy {
if u.Atomic { if u.Atomic {
u.Wait = kube.StatusWatcherStrategy u.Wait = kube.StatusWatcherStrategy
} }
@ -190,10 +190,6 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
return res, nil return res, nil
} }
func (u *Upgrade) shouldWait() bool {
return u.Wait != ""
}
// isDryRun returns true if Upgrade is set to run as a DryRun // isDryRun returns true if Upgrade is set to run as a DryRun
func (u *Upgrade) isDryRun() bool { func (u *Upgrade) isDryRun() bool {
if u.DryRun || u.DryRunOption == "client" || u.DryRunOption == "server" || u.DryRunOption == "true" { if u.DryRun || u.DryRunOption == "client" || u.DryRunOption == "server" || u.DryRunOption == "true" {
@ -451,22 +447,17 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
} }
} }
if u.shouldWait() { if u.WaitForJobs {
u.cfg.Log( if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
"waiting for release %s resources (created: %d updated: %d deleted: %d)", u.cfg.recordRelease(originalRelease)
upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted)) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
if u.WaitForJobs { return
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { }
u.cfg.recordRelease(originalRelease) } else {
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
return u.cfg.recordRelease(originalRelease)
} u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
} else { return
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
return
}
} }
} }
@ -534,7 +525,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
rollin := NewRollback(u.cfg) rollin := NewRollback(u.cfg)
rollin.Version = filteredHistory[0].Version rollin.Version = filteredHistory[0].Version
if !u.shouldWait() { if u.Wait == kube.HookOnlyStrategy {
rollin.Wait = kube.StatusWatcherStrategy rollin.Wait = kube.StatusWatcherStrategy
} }
rollin.WaitForJobs = u.WaitForJobs rollin.WaitForJobs = u.WaitForJobs

@ -84,7 +84,8 @@ type WaitStrategy string
const ( const (
StatusWatcherStrategy WaitStrategy = "watcher" StatusWatcherStrategy WaitStrategy = "watcher"
LegacyWaiterStrategy WaitStrategy = "legacy" LegacyStrategy WaitStrategy = "legacy"
HookOnlyStrategy WaitStrategy = "noop"
) )
func init() { func init() {
@ -98,36 +99,46 @@ func init() {
} }
} }
func (c *Client) newStatusWatcher() (*statusWaiter, error) {
cfg, err := c.Factory.ToRESTConfig()
if err != nil {
return nil, err
}
dynamicClient, err := c.Factory.DynamicClient()
if err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(cfg)
if err != nil {
return nil, err
}
restMapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient)
if err != nil {
return nil, err
}
return &statusWaiter{
restMapper: restMapper,
client: dynamicClient,
log: c.Log,
}, nil
}
func (c *Client) newWaiter(strategy WaitStrategy) (Waiter, error) { func (c *Client) newWaiter(strategy WaitStrategy) (Waiter, error) {
switch strategy { switch strategy {
case LegacyWaiterStrategy: case LegacyStrategy:
kc, err := c.Factory.KubernetesClientSet() kc, err := c.Factory.KubernetesClientSet()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &HelmWaiter{kubeClient: kc, log: c.Log}, nil return &HelmWaiter{kubeClient: kc, log: c.Log}, nil
case StatusWatcherStrategy: case StatusWatcherStrategy:
cfg, err := c.Factory.ToRESTConfig() return c.newStatusWatcher()
if err != nil { case HookOnlyStrategy:
return nil, err sw, err := c.newStatusWatcher()
}
dynamicClient, err := c.Factory.DynamicClient()
if err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(cfg)
if err != nil {
return nil, err
}
restMapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &statusWaiter{ return &hookOnlyWaiter{sw: sw}, nil
restMapper: restMapper,
client: dynamicClient,
log: c.Log,
}, nil
default: default:
return nil, errors.New("unknown wait strategy") return nil, errors.New("unknown wait strategy")
} }

@ -513,7 +513,7 @@ func TestWait(t *testing.T) {
}), }),
} }
var err error var err error
c.Waiter, err = c.newWaiter(LegacyWaiterStrategy) c.Waiter, err = c.newWaiter(LegacyStrategy)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -570,7 +570,7 @@ func TestWaitJob(t *testing.T) {
}), }),
} }
var err error var err error
c.Waiter, err = c.newWaiter(LegacyWaiterStrategy) c.Waiter, err = c.newWaiter(LegacyStrategy)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -629,7 +629,7 @@ func TestWaitDelete(t *testing.T) {
}), }),
} }
var err error var err error
c.Waiter, err = c.newWaiter(LegacyWaiterStrategy) c.Waiter, err = c.newWaiter(LegacyStrategy)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -209,3 +209,23 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logFn func
} }
} }
} }
type hookOnlyWaiter struct {
sw *statusWaiter
}
func (w *hookOnlyWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error {
return w.sw.WatchUntilReady(resourceList, timeout)
}
func (w *hookOnlyWaiter) Wait(resourceList ResourceList, timeout time.Duration) error {
return nil
}
func (w *hookOnlyWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error {
return nil
}
func (w *hookOnlyWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error {
return nil
}

Loading…
Cancel
Save