diff --git a/pkg/cmd/wait/wait.go b/pkg/cmd/wait/wait.go index e211ccec..f453d4ba 100644 --- a/pkg/cmd/wait/wait.go +++ b/pkg/cmd/wait/wait.go @@ -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) }