Add new --wait-for-creation flag in kubectl wait command

kubectl wait command errors out when the waited resource does not exist.
But we need to provide a way to the users about intentionally also waiting for
the creation of resources.

This PR introduces a new flag to cover waiting for the creation of resources
with preserving the default behavior.

Kubernetes-commit: e24b9a022f3b1e97ea538c9754d4d38f119f275e
This commit is contained in:
Arda Güçlü 2024-01-26 15:53:55 +03:00 committed by Kubernetes Publisher
parent 6921b6008e
commit 64a1fe556b
1 changed files with 54 additions and 13 deletions

View File

@ -82,7 +82,10 @@ var (
# Wait for the pod "busybox1" to be deleted, with a timeout of 60s, after having issued the "delete" command # Wait for the pod "busybox1" to be deleted, with a timeout of 60s, after having issued the "delete" command
kubectl delete pod/busybox1 kubectl delete pod/busybox1
kubectl wait --for=delete pod/busybox1 --timeout=60s`)) kubectl wait --for=delete pod/busybox1 --timeout=60s
# Wait for the creation of the service "loadbalancer" in addition to wait to have ingress
kubectl wait --for=jsonpath='{.status.loadBalancer.ingress}' service/loadbalancer --wait-for-creation`))
) )
// errNoMatchingResources is returned when there is no resources matching a query. // errNoMatchingResources is returned when there is no resources matching a query.
@ -98,6 +101,7 @@ type WaitFlags struct {
Timeout time.Duration Timeout time.Duration
ForCondition string ForCondition string
WaitForCreation bool
genericiooptions.IOStreams genericiooptions.IOStreams
} }
@ -116,6 +120,7 @@ func NewWaitFlags(restClientGetter genericclioptions.RESTClientGetter, streams g
WithLatest(), WithLatest(),
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
WaitForCreation: true,
IOStreams: streams, IOStreams: streams,
} }
@ -152,6 +157,7 @@ func (flags *WaitFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().DurationVar(&flags.Timeout, "timeout", flags.Timeout, "The length of time to wait before giving up. Zero means check once and don't wait, negative means wait for a week.") cmd.Flags().DurationVar(&flags.Timeout, "timeout", flags.Timeout, "The length of time to wait before giving up. Zero means check once and don't wait, negative means wait for a week.")
cmd.Flags().StringVar(&flags.ForCondition, "for", flags.ForCondition, "The condition to wait on: [delete|condition=condition-name[=condition-value]|jsonpath='{JSONPath expression}'=[JSONPath value]]. The default condition-value is true. Condition values are compared after Unicode simple case folding, which is a more general form of case-insensitivity.") cmd.Flags().StringVar(&flags.ForCondition, "for", flags.ForCondition, "The condition to wait on: [delete|condition=condition-name[=condition-value]|jsonpath='{JSONPath expression}'=[JSONPath value]]. The default condition-value is true. Condition values are compared after Unicode simple case folding, which is a more general form of case-insensitivity.")
cmd.Flags().BoolVar(&flags.WaitForCreation, "wait-for-creation", flags.WaitForCreation, "The default value is true. If set to true, also wait for creation of objects if they do not already exist. This flag is ignored in --for=delete")
} }
// ToOptions converts from CLI inputs to runtime inputs // ToOptions converts from CLI inputs to runtime inputs
@ -184,6 +190,7 @@ func (flags *WaitFlags) ToOptions(args []string) (*WaitOptions, error) {
DynamicClient: dynamicClient, DynamicClient: dynamicClient,
Timeout: effectiveTimeout, Timeout: effectiveTimeout,
ForCondition: flags.ForCondition, ForCondition: flags.ForCondition,
WaitForCreation: flags.WaitForCreation,
Printer: printer, Printer: printer,
ConditionFn: conditionFn, ConditionFn: conditionFn,
@ -306,6 +313,7 @@ type WaitOptions struct {
DynamicClient dynamic.Interface DynamicClient dynamic.Interface
Timeout time.Duration Timeout time.Duration
ForCondition string ForCondition string
WaitForCreation bool
Printer printers.ResourcePrinter Printer printers.ResourcePrinter
ConditionFn ConditionFunc ConditionFn ConditionFunc
@ -320,6 +328,40 @@ func (o *WaitOptions) RunWait() error {
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), o.Timeout) ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), o.Timeout)
defer cancel() defer cancel()
isForDelete := strings.ToLower(o.ForCondition) == "delete"
if o.WaitForCreation && o.Timeout == 0 {
return fmt.Errorf("--wait-for-creation requires a timeout value greater than 0")
}
if o.WaitForCreation && !isForDelete {
err := func() error {
for {
select {
case <-ctx.Done():
return fmt.Errorf("context deadline is exceeded while waiting for the creation of the resources")
default:
err := o.ResourceFinder.Do().Visit(func(info *resource.Info, err error) error {
// We don't need to do anything after we assure that the resources exist. Because
// actual logic will be incorporated after we wait all the resources' existence.
return nil
})
// It is verified that all the resources exist.
if err == nil {
return nil
}
// We specifically wait for the creation of resources and all the errors
// other than not found means that this is something we cannot handle.
if !apierrors.IsNotFound(err) {
return err
}
}
}
}()
if err != nil {
return err
}
}
visitCount := 0 visitCount := 0
visitFunc := func(info *resource.Info, err error) error { visitFunc := func(info *resource.Info, err error) error {
if err != nil { if err != nil {
@ -338,7 +380,6 @@ func (o *WaitOptions) RunWait() error {
return err return err
} }
visitor := o.ResourceFinder.Do() visitor := o.ResourceFinder.Do()
isForDelete := strings.ToLower(o.ForCondition) == "delete"
if visitor, ok := visitor.(*resource.Result); ok && isForDelete { if visitor, ok := visitor.(*resource.Result); ok && isForDelete {
visitor.IgnoreErrors(apierrors.IsNotFound) visitor.IgnoreErrors(apierrors.IsNotFound)
} }