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
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.
@ -96,8 +99,9 @@ type WaitFlags struct {
PrintFlags *genericclioptions.PrintFlags
ResourceBuilderFlags *genericclioptions.ResourceBuilderFlags
Timeout time.Duration
ForCondition string
Timeout time.Duration
ForCondition string
WaitForCreation bool
genericiooptions.IOStreams
}
@ -115,7 +119,8 @@ func NewWaitFlags(restClientGetter genericclioptions.RESTClientGetter, streams g
WithLocal(false).
WithLatest(),
Timeout: 30 * time.Second,
Timeout: 30 * time.Second,
WaitForCreation: true,
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().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
@ -180,10 +186,11 @@ func (flags *WaitFlags) ToOptions(args []string) (*WaitOptions, error) {
}
o := &WaitOptions{
ResourceFinder: builder,
DynamicClient: dynamicClient,
Timeout: effectiveTimeout,
ForCondition: flags.ForCondition,
ResourceFinder: builder,
DynamicClient: dynamicClient,
Timeout: effectiveTimeout,
ForCondition: flags.ForCondition,
WaitForCreation: flags.WaitForCreation,
Printer: printer,
ConditionFn: conditionFn,
@ -302,10 +309,11 @@ type WaitOptions struct {
ResourceFinder genericclioptions.ResourceFinder
// UIDMap maps a resource location to a UID. It is optional, but ConditionFuncs may choose to use it to make the result
// more reliable. For instance, delete can look for UID consistency during delegated calls.
UIDMap UIDMap
DynamicClient dynamic.Interface
Timeout time.Duration
ForCondition string
UIDMap UIDMap
DynamicClient dynamic.Interface
Timeout time.Duration
ForCondition string
WaitForCreation bool
Printer printers.ResourcePrinter
ConditionFn ConditionFunc
@ -320,6 +328,40 @@ func (o *WaitOptions) RunWait() error {
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), o.Timeout)
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
visitFunc := func(info *resource.Info, err error) error {
if err != nil {
@ -338,7 +380,6 @@ func (o *WaitOptions) RunWait() error {
return err
}
visitor := o.ResourceFinder.Do()
isForDelete := strings.ToLower(o.ForCondition) == "delete"
if visitor, ok := visitor.(*resource.Result); ok && isForDelete {
visitor.IgnoreErrors(apierrors.IsNotFound)
}