// WaitForRunningBuild waits until the specified build is no longer New or Pending. Returns true if // the build ran within timeout, false if it did not, and an error if any other error state occurred. // The last observed Build state is returned. func WaitForRunningBuild(watcher rest.Watcher, ctx kapi.Context, build *api.Build, timeout time.Duration) (*api.Build, bool, error) { fieldSelector := unversioned.FieldSelector{Selector: fields.Set{"metadata.name": build.Name}.AsSelector()} options := &unversioned.ListOptions{FieldSelector: fieldSelector, ResourceVersion: build.ResourceVersion} w, err := watcher.Watch(ctx, options) if err != nil { return nil, false, err } defer w.Stop() ch := w.ResultChan() observed := build expire := time.After(timeout) for { select { case event := <-ch: obj, ok := event.Object.(*api.Build) if !ok { return observed, false, fmt.Errorf("received unknown object while watching for builds") } observed = obj switch obj.Status.Phase { case api.BuildPhaseRunning, api.BuildPhaseComplete, api.BuildPhaseFailed, api.BuildPhaseError, api.BuildPhaseCancelled: return observed, true, nil case api.BuildPhaseNew, api.BuildPhasePending: default: return observed, false, ErrUnknownBuildPhase } case <-expire: return observed, false, nil } } }
// watchForResourceVersion watches for an Add/Modify event matching the given resourceVersion. // If an error, timeout, or unexpected event is received, an error is returned. // If an add/modify event is observed with the correct resource version, nil is returned. func watchForResourceVersion(versioner storage.Versioner, watcher rest.Watcher, resourceVersion string, timeout time.Duration) error { // Watch from the previous resource version, so the first watch event is the desired version previousVersion, err := previousResourceVersion(versioner, resourceVersion) if err != nil { return err } w, err := watcher.Watch(context.TODO(), &kapi.ListOptions{ResourceVersion: previousVersion}) if err != nil { return fmt.Errorf("error verifying resourceVersion %s: %v", resourceVersion, err) } defer w.Stop() select { case event := <-w.ResultChan(): if event.Type != watch.Added && event.Type != watch.Modified { return fmt.Errorf("unexpected watch event verifying resourceVersion %s: %q", resourceVersion, event.Type) } if event.Object == nil { return fmt.Errorf("unexpected watch event verifying resourceVersion %s: object was nil", resourceVersion) } accessor, err := meta.Accessor(event.Object) if err != nil { return err } actualResourceVersion := accessor.GetResourceVersion() if actualResourceVersion != resourceVersion { return fmt.Errorf("unexpected watch event verifying resourceVersion %s: resource version was %s)", resourceVersion, actualResourceVersion) } return nil case <-time.After(timeout): return fmt.Errorf("timeout verifying resourceVersion %s", resourceVersion) } }
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, err := scope.Namer.Namespace(req) if err != nil { errorJSON(err, scope.Codec, w) return } // Watches for single objects are routed to this function. // Treat a /name parameter the same as a field selector entry. hasName := true _, name, err := scope.Namer.Name(req) if err != nil { hasName = false } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) versioned, err := scope.Creater.New(scope.ServerAPIVersion, "ListOptions") if err != nil { errorJSON(err, scope.Codec, w) return } if err := scope.Codec.DecodeParametersInto(req.Request.URL.Query(), versioned); err != nil { errorJSON(err, scope.Codec, w) return } opts := api.ListOptions{} if err := scope.Convertor.Convert(versioned, &opts); err != nil { errorJSON(err, scope.Codec, w) return } // transform fields // TODO: Should this be done as part of convertion? fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value) } if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) errorJSON(err, scope.Codec, w) return } if hasName { // metadata.name is the canonical internal name. // generic.SelectionPredicate will notice that this is // a request for a single object and optimize the // storage query accordingly. nameSelector := fields.OneTermEqualSelector("metadata.name", name) if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { // It doesn't make sense to ask for both a name // and a field selector, since just the name is // sufficient to narrow down the request to a // single object. errorJSON( errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), scope.Codec, w, ) return } opts.FieldSelector = nameSelector } if (opts.Watch || forceWatch) && rw != nil { watcher, err := rw.Watch(ctx, &opts) if err != nil { errorJSON(err, scope.Codec, w) return } // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. timeout := time.Duration(0) if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second } if timeout == 0 && minRequestTimeout > 0 { timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) } serveWatch(watcher, scope, w, req, timeout) return } result, err := r.List(ctx, &opts) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setListSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("List " + req.Request.URL.Path) w := res.ResponseWriter namespace, err := scope.Namer.Namespace(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // Watches for single objects are routed to this function. // Treat a /name parameter the same as a field selector entry. hasName := true _, name, err := scope.Namer.Name(req) if err != nil { hasName = false } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) opts := api.ListOptions{} if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &opts); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // transform fields // TODO: DecodeParametersInto should do this. if opts.FieldSelector != nil { fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) } if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) scope.err(err, res.ResponseWriter, req.Request) return } } if hasName { // metadata.name is the canonical internal name. // generic.SelectionPredicate will notice that this is // a request for a single object and optimize the // storage query accordingly. nameSelector := fields.OneTermEqualSelector("metadata.name", name) if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { // It doesn't make sense to ask for both a name // and a field selector, since just the name is // sufficient to narrow down the request to a // single object. scope.err(errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), res.ResponseWriter, req.Request) return } opts.FieldSelector = nameSelector } if (opts.Watch || forceWatch) && rw != nil { watcher, err := rw.Watch(ctx, &opts) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. timeout := time.Duration(0) if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second } if timeout == 0 && minRequestTimeout > 0 { timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) } serveWatch(watcher, scope, req, res, timeout) return } // Log only long List requests (ignore Watch). defer trace.LogIfLong(500 * time.Millisecond) trace.Step("About to List from storage") result, err := r.List(ctx, &opts) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Listing from storage done") numberOfItems, err := setListSelfLink(result, req, scope.Namer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Self-linking done") write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems)) } }