diff --git a/go.mod b/go.mod index 8302783bd..eb13904df 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( golang.org/x/sys v0.20.0 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.0.0-20240620180645-9f6f03ff043b - k8s.io/apimachinery v0.0.0-20240620180412-04fe5186a7b1 + k8s.io/api v0.0.0-20240620180646-e09016fffd8e + k8s.io/apimachinery v0.0.0-20240620220412-eb26334eeb0f k8s.io/cli-runtime v0.0.0-20240620184121-8e480ebaa098 k8s.io/client-go v0.0.0-20240620181025-b9309ac26b16 k8s.io/component-base v0.0.0-20240620181933-0407e51f2497 diff --git a/go.sum b/go.sum index 5e5b73593..fac3c2172 100644 --- a/go.sum +++ b/go.sum @@ -277,10 +277,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.0.0-20240620180645-9f6f03ff043b h1:MlO1MxAa8kX7fiaDMVyFJYqqancoCHGggG9UurjrKNc= -k8s.io/api v0.0.0-20240620180645-9f6f03ff043b/go.mod h1:1MT1m3FZiytU3Iz+RVL9/0UV96hiPGA1mPCSCgP0QTk= -k8s.io/apimachinery v0.0.0-20240620180412-04fe5186a7b1 h1:avzIPRkvVSlb207+JV7eZv498CJ0EBDMKvwmcTS3m2k= -k8s.io/apimachinery v0.0.0-20240620180412-04fe5186a7b1/go.mod h1:wKr/cy6yAwcKdBBcxyg2m/xTdMwHdCRNo7Wt2GxZEP8= +k8s.io/api v0.0.0-20240620180646-e09016fffd8e h1:sikgfs6oUZr6NPm/El/AQPSUb8OFsnI4Vkwch/o4l1g= +k8s.io/api v0.0.0-20240620180646-e09016fffd8e/go.mod h1:1MT1m3FZiytU3Iz+RVL9/0UV96hiPGA1mPCSCgP0QTk= +k8s.io/apimachinery v0.0.0-20240620220412-eb26334eeb0f h1:JnekvpSdfvWpcvCIbuU0rMFutVA4W9KosWY8HYLcjMw= +k8s.io/apimachinery v0.0.0-20240620220412-eb26334eeb0f/go.mod h1:wKr/cy6yAwcKdBBcxyg2m/xTdMwHdCRNo7Wt2GxZEP8= k8s.io/cli-runtime v0.0.0-20240620184121-8e480ebaa098 h1:KxwXNJlEbq8gzldnFPCMpSRb1r2t3mVsssC/+YxQqOU= k8s.io/cli-runtime v0.0.0-20240620184121-8e480ebaa098/go.mod h1:CZrbWDD+2kp0D0v1mc9T3h2g/73NqyzO42y6FX838hE= k8s.io/client-go v0.0.0-20240620181025-b9309ac26b16 h1:ws4qTqL+vL0yr8xFKOlcRFO926YHDvwJnjgVAHQJdTs= diff --git a/pkg/cmd/wait/wait.go b/pkg/cmd/wait/wait.go index e211ccec6..f453d4ba6 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) }