Merge pull request #44294 from deads2k/server-16-gorestful
Automatic merge from submit-queue remove dependency on gorestful for rest handling There's no reason for us to rely upon go-restful for our REST handling. This separates the layers so that gorestful route functions are built on top of native `http.HandlerFuncs`. @DirectXMan12 I think this is the sort of handling you wanted to be able to add and remove at will, right? I have other commits that demonstrate how to wire these into "normal" `http.Handlers` if its useful to you. @kubernetes/sig-api-machinery-pr-reviews @smarterclayton @sttts @lavalamp
This commit is contained in:
		| @@ -679,6 +679,29 @@ func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, strin | ||||
|  | ||||
| var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{} | ||||
|  | ||||
| type GetWithOptionsRootRESTStorage struct { | ||||
| 	*SimpleTypedStorage | ||||
| 	optionsReceived runtime.Object | ||||
| 	takesPath       string | ||||
| } | ||||
|  | ||||
| func (r *GetWithOptionsRootRESTStorage) Get(ctx request.Context, name string, options runtime.Object) (runtime.Object, error) { | ||||
| 	if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok { | ||||
| 		return nil, fmt.Errorf("Unexpected options object: %#v", options) | ||||
| 	} | ||||
| 	r.optionsReceived = options | ||||
| 	return r.SimpleTypedStorage.Get(ctx, name, &metav1.GetOptions{}) | ||||
| } | ||||
|  | ||||
| func (r *GetWithOptionsRootRESTStorage) NewGetOptions() (runtime.Object, bool, string) { | ||||
| 	if len(r.takesPath) > 0 { | ||||
| 		return &genericapitesting.SimpleGetOptions{}, true, r.takesPath | ||||
| 	} | ||||
| 	return &genericapitesting.SimpleGetOptions{}, false, "" | ||||
| } | ||||
|  | ||||
| var _ rest.GetterWithOptions = &GetWithOptionsRootRESTStorage{} | ||||
|  | ||||
| type NamedCreaterRESTStorage struct { | ||||
| 	*SimpleRESTStorage | ||||
| 	createdName string | ||||
| @@ -1519,48 +1542,49 @@ func TestGetWithOptionsRouteParams(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestGetWithOptions(t *testing.T) { | ||||
| 	storage := map[string]rest.Storage{} | ||||
| 	simpleStorage := GetWithOptionsRESTStorage{ | ||||
| 		SimpleRESTStorage: &SimpleRESTStorage{ | ||||
| 			item: genericapitesting.Simple{ | ||||
| 				Other: "foo", | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name         string | ||||
| 		rootScoped   bool | ||||
| 		requestURL   string | ||||
| 		expectedPath string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "basic", | ||||
| 			requestURL:   "/namespaces/default/simple/id?param1=test1¶m2=test2", | ||||
| 			expectedPath: "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "with path", | ||||
| 			requestURL:   "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2", | ||||
| 			expectedPath: "a/different/path", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "as subresource", | ||||
| 			requestURL:   "/namespaces/default/simple/id/subresource/another/different/path?param1=test1¶m2=test2", | ||||
| 			expectedPath: "another/different/path", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "cluster-scoped basic", | ||||
| 			rootScoped:   true, | ||||
| 			requestURL:   "/simple/id?param1=test1¶m2=test2", | ||||
| 			expectedPath: "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "cluster-scoped basic with path", | ||||
| 			rootScoped:   true, | ||||
| 			requestURL:   "/simple/id/a/cluster/path?param1=test1¶m2=test2", | ||||
| 			expectedPath: "a/cluster/path", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "cluster-scoped basic as subresource", | ||||
| 			rootScoped:   true, | ||||
| 			requestURL:   "/simple/id/subresource/another/cluster/path?param1=test1¶m2=test2", | ||||
| 			expectedPath: "another/cluster/path", | ||||
| 		}, | ||||
| 	} | ||||
| 	storage["simple"] = &simpleStorage | ||||
| 	handler := handle(storage) | ||||
| 	server := httptest.NewServer(handler) | ||||
| 	defer server.Close() | ||||
|  | ||||
| 	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id?param1=test1¶m2=test2") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		t.Fatalf("unexpected response: %#v", resp) | ||||
| 	} | ||||
| 	var itemOut genericapitesting.Simple | ||||
| 	body, err := extractBody(resp, &itemOut) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if itemOut.Name != simpleStorage.item.Name { | ||||
| 		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) | ||||
| 	} | ||||
|  | ||||
| 	opts, ok := simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) | ||||
| 	if !ok { | ||||
| 		t.Errorf("Unexpected options object received: %#v", simpleStorage.optionsReceived) | ||||
| 		return | ||||
| 	} | ||||
| 	if opts.Param1 != "test1" || opts.Param2 != "test2" { | ||||
| 		t.Errorf("Did not receive expected options: %#v", opts) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetWithOptionsAndPath(t *testing.T) { | ||||
| 	storage := map[string]rest.Storage{} | ||||
| 	for _, test := range tests { | ||||
| 		simpleStorage := GetWithOptionsRESTStorage{ | ||||
| 			SimpleRESTStorage: &SimpleRESTStorage{ | ||||
| 				item: genericapitesting.Simple{ | ||||
| @@ -1569,37 +1593,72 @@ func TestGetWithOptionsAndPath(t *testing.T) { | ||||
| 			}, | ||||
| 			takesPath: "atAPath", | ||||
| 		} | ||||
| 		simpleRootStorage := GetWithOptionsRootRESTStorage{ | ||||
| 			SimpleTypedStorage: &SimpleTypedStorage{ | ||||
| 				baseType: &genericapitesting.SimpleRoot{}, // a root scoped type | ||||
| 				item: &genericapitesting.SimpleRoot{ | ||||
| 					Other: "foo", | ||||
| 				}, | ||||
| 			}, | ||||
| 			takesPath: "atAPath", | ||||
| 		} | ||||
|  | ||||
| 		storage := map[string]rest.Storage{} | ||||
| 		if test.rootScoped { | ||||
| 			storage["simple"] = &simpleRootStorage | ||||
| 			storage["simple/subresource"] = &simpleRootStorage | ||||
| 		} else { | ||||
| 			storage["simple"] = &simpleStorage | ||||
| 			storage["simple/subresource"] = &simpleStorage | ||||
| 		} | ||||
| 		handler := handle(storage) | ||||
| 		server := httptest.NewServer(handler) | ||||
| 		defer server.Close() | ||||
|  | ||||
| 	resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2&atAPath=not") | ||||
| 		resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + test.requestURL) | ||||
| 		if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 			t.Errorf("%s: %v", test.name, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if resp.StatusCode != http.StatusOK { | ||||
| 		t.Fatalf("unexpected response: %#v", resp) | ||||
| 			t.Errorf("%s: unexpected response: %#v", test.name, resp) | ||||
| 			continue | ||||
| 		} | ||||
| 		var itemOut genericapitesting.Simple | ||||
| 		body, err := extractBody(resp, &itemOut) | ||||
| 		if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 			t.Errorf("%s: %v", test.name, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if itemOut.Name != simpleStorage.item.Name { | ||||
| 		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) | ||||
| 			t.Errorf("%s: Unexpected data: %#v, expected %#v (%s)", test.name, itemOut, simpleStorage.item, string(body)) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 	opts, ok := simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) | ||||
| 		var opts *genericapitesting.SimpleGetOptions | ||||
| 		var ok bool | ||||
| 		if test.rootScoped { | ||||
| 			opts, ok = simpleRootStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) | ||||
| 		} else { | ||||
| 			opts, ok = simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) | ||||
|  | ||||
| 		} | ||||
| 		if !ok { | ||||
| 		t.Errorf("Unexpected options object received: %#v", simpleStorage.optionsReceived) | ||||
| 		return | ||||
| 			t.Errorf("%s: Unexpected options object received: %#v", test.name, simpleStorage.optionsReceived) | ||||
| 			continue | ||||
| 		} | ||||
| 	if opts.Param1 != "test1" || opts.Param2 != "test2" || opts.Path != "a/different/path" { | ||||
| 		t.Errorf("Did not receive expected options: %#v", opts) | ||||
| 		if opts.Param1 != "test1" || opts.Param2 != "test2" { | ||||
| 			t.Errorf("%s: Did not receive expected options: %#v", test.name, opts) | ||||
| 			continue | ||||
| 		} | ||||
| 		if opts.Path != test.expectedPath { | ||||
| 			t.Errorf("%s: Unexpected path value. Expected: %s. Actual: %s.", test.name, test.expectedPath, opts.Path) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetAlternateSelfLink(t *testing.T) { | ||||
| 	storage := map[string]rest.Storage{} | ||||
| 	simpleStorage := SimpleRESTStorage{ | ||||
|   | ||||
| @@ -27,7 +27,6 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/emicklei/go-restful" | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| @@ -78,48 +77,47 @@ func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Reque | ||||
|  | ||||
| // getterFunc performs a get request with the given context and object name. The request | ||||
| // may be used to deserialize an options object to pass to the getter. | ||||
| type getterFunc func(ctx request.Context, name string, req *restful.Request) (runtime.Object, error) | ||||
| type getterFunc func(ctx request.Context, name string, req *http.Request) (runtime.Object, error) | ||||
|  | ||||
| // MaxRetryWhenPatchConflicts is the maximum number of conflicts retry during a patch operation before returning failure | ||||
| const MaxRetryWhenPatchConflicts = 5 | ||||
|  | ||||
| // getResourceHandler is an HTTP handler function for get requests. It delegates to the | ||||
| // passed-in getterFunc to perform the actual get. | ||||
| func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		w := res.ResponseWriter | ||||
| 		namespace, name, err := scope.Namer.Name(req.Request) | ||||
| func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		namespace, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		result, err := getter(ctx, name, req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		if err := setSelfLink(result, req, scope.Namer); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetResource returns a function that handles retrieving a single resource from a rest.Storage object. | ||||
| func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) restful.RouteFunction { | ||||
| func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.HandlerFunc { | ||||
| 	return getResourceHandler(scope, | ||||
| 		func(ctx request.Context, name string, req *restful.Request) (runtime.Object, error) { | ||||
| 		func(ctx request.Context, name string, req *http.Request) (runtime.Object, error) { | ||||
| 			// For performance tracking purposes. | ||||
| 			trace := utiltrace.New("Get " + req.Request.URL.Path) | ||||
| 			trace := utiltrace.New("Get " + req.URL.Path) | ||||
| 			defer trace.LogIfLong(500 * time.Millisecond) | ||||
|  | ||||
| 			// check for export | ||||
| 			options := metav1.GetOptions{} | ||||
| 			if values := req.Request.URL.Query(); len(values) > 0 { | ||||
| 			if values := req.URL.Query(); len(values) > 0 { | ||||
| 				exports := metav1.ExportOptions{} | ||||
| 				if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil { | ||||
| 					return nil, err | ||||
| @@ -140,48 +138,55 @@ func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) restful.Rou | ||||
| } | ||||
|  | ||||
| // GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object. | ||||
| func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope) restful.RouteFunction { | ||||
| func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubresource bool) http.HandlerFunc { | ||||
| 	return getResourceHandler(scope, | ||||
| 		func(ctx request.Context, name string, req *restful.Request) (runtime.Object, error) { | ||||
| 		func(ctx request.Context, name string, req *http.Request) (runtime.Object, error) { | ||||
| 			opts, subpath, subpathKey := r.NewGetOptions() | ||||
| 			if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { | ||||
| 			if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			return r.Get(ctx, name, opts) | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| func getRequestOptions(req *restful.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string) error { | ||||
| // getRequestOptions parses out options and can include path information.  The path information shouldn't include the subresource. | ||||
| func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error { | ||||
| 	if into == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	query := req.Request.URL.Query() | ||||
| 	query := req.URL.Query() | ||||
| 	if subpath { | ||||
| 		newQuery := make(url.Values) | ||||
| 		for k, v := range query { | ||||
| 			newQuery[k] = v | ||||
| 		} | ||||
| 		newQuery[subpathKey] = []string{req.PathParameter("path")} | ||||
|  | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		requestInfo, _ := request.RequestInfoFrom(ctx) | ||||
| 		startingIndex := 2 | ||||
| 		if isSubresource { | ||||
| 			startingIndex = 3 | ||||
| 		} | ||||
| 		newQuery[subpathKey] = []string{strings.Join(requestInfo.Parts[startingIndex:], "/")} | ||||
| 		query = newQuery | ||||
| 	} | ||||
| 	return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into) | ||||
| } | ||||
|  | ||||
| // ConnectResource returns a function that handles a connect request on a rest.Storage object. | ||||
| func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		w := res.ResponseWriter | ||||
| 		namespace, name, err := scope.Namer.Name(req.Request) | ||||
| func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		namespace, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
| 		opts, subpath, subpathKey := connecter.NewConnectOptions() | ||||
| 		if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 		if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		if admit.Handles(admission.Connect) { | ||||
| @@ -194,62 +199,59 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi | ||||
|  | ||||
| 			err = admit.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo)) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, res: res}) | ||||
| 		handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w}) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		handler.ServeHTTP(w, req.Request) | ||||
| 		handler.ServeHTTP(w, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // responder implements rest.Responder for assisting a connector in writing objects or errors. | ||||
| type responder struct { | ||||
| 	scope RequestScope | ||||
| 	req   *restful.Request | ||||
| 	res   *restful.Response | ||||
| 	req   *http.Request | ||||
| 	w     http.ResponseWriter | ||||
| } | ||||
|  | ||||
| func (r *responder) Object(statusCode int, obj runtime.Object) { | ||||
| 	responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.res.ResponseWriter, r.req.Request) | ||||
| 	responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.w, r.req) | ||||
| } | ||||
|  | ||||
| func (r *responder) Error(err error) { | ||||
| 	r.scope.err(err, r.res.ResponseWriter, r.req.Request) | ||||
| 	r.scope.err(err, r.w, r.req) | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// For performance tracking purposes. | ||||
| 		trace := utiltrace.New("List " + req.Request.URL.Path) | ||||
| 		trace := utiltrace.New("List " + req.URL.Path) | ||||
|  | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| 		namespace, err := scope.Namer.Namespace(req.Request) | ||||
| 		namespace, err := scope.Namer.Namespace(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			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.Request) | ||||
| 		_, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			hasName = false | ||||
| 		} | ||||
|  | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		opts := metainternalversion.ListOptions{} | ||||
| 		if err := metainternalversion.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 		if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -262,7 +264,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch | ||||
| 			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) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| @@ -278,7 +280,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch | ||||
| 				// 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) | ||||
| 				scope.err(errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), w, req) | ||||
| 				return | ||||
| 			} | ||||
| 			opts.FieldSelector = nameSelector | ||||
| @@ -293,14 +295,14 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch | ||||
| 			if timeout == 0 && minRequestTimeout > 0 { | ||||
| 				timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) | ||||
| 			} | ||||
| 			glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.Request.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout) | ||||
| 			glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout) | ||||
|  | ||||
| 			watcher, err := rw.Watch(ctx, &opts) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 			serveWatch(watcher, scope, req, res, timeout) | ||||
| 			serveWatch(watcher, scope, req, w, timeout) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -309,67 +311,65 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch | ||||
| 		trace.Step("About to List from storage") | ||||
| 		result, err := r.List(ctx, &opts) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Listing from storage done") | ||||
| 		numberOfItems, err := setListSelfLink(result, req, scope.Namer) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Self-linking done") | ||||
| 		// Ensure empty lists return a non-nil items slice | ||||
| 		if numberOfItems == 0 && meta.IsListType(result) { | ||||
| 			if err := meta.SetList(result, []runtime.Object{}); err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 		trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// For performance tracking purposes. | ||||
| 		trace := utiltrace.New("Create " + req.Request.URL.Path) | ||||
| 		trace := utiltrace.New("Create " + req.URL.Path) | ||||
| 		defer trace.LogIfLong(500 * time.Millisecond) | ||||
|  | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| 		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) | ||||
| 		timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) | ||||
| 		timeout := parseTimeout(req.URL.Query().Get("timeout")) | ||||
|  | ||||
| 		var ( | ||||
| 			namespace, name string | ||||
| 			err             error | ||||
| 		) | ||||
| 		if includeName { | ||||
| 			namespace, name, err = scope.Namer.Name(req.Request) | ||||
| 			namespace, name, err = scope.Namer.Name(req) | ||||
| 		} else { | ||||
| 			namespace, err = scope.Namer.Namespace(req.Request) | ||||
| 			namespace, err = scope.Namer.Namespace(req) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		gv := scope.Kind.GroupVersion() | ||||
| 		s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer) | ||||
| 		s, err := negotiation.NegotiateInputSerializer(req, scope.Serializer) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}) | ||||
|  | ||||
| 		body, err := readBody(req.Request) | ||||
| 		body, err := readBody(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -379,12 +379,12 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object | ||||
| 		obj, gvk, err := decoder.Decode(body, &defaultGVK, original) | ||||
| 		if err != nil { | ||||
| 			err = transformDecodeError(typer, err, original, gvk, body) | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		if gvk.GroupVersion() != gv { | ||||
| 			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String())) | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Conversion done") | ||||
| @@ -394,7 +394,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object | ||||
|  | ||||
| 			err = admit.Admit(admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| @@ -408,28 +408,28 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object | ||||
| 			return out, err | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Object stored in database") | ||||
|  | ||||
| 		if err := setSelfLink(result, req, scope.Namer); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Self-link added") | ||||
|  | ||||
| 		responsewriters.WriteObject(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateNamedResource returns a function that will handle a resource creation with name. | ||||
| func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) http.HandlerFunc { | ||||
| 	return createHandler(r, scope, typer, admit, true) | ||||
| } | ||||
|  | ||||
| // CreateResource returns a function that will handle a resource creation. | ||||
| func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) http.HandlerFunc { | ||||
| 	return createHandler(&namedCreaterAdapter{r}, scope, typer, admit, false) | ||||
| } | ||||
|  | ||||
| @@ -443,47 +443,45 @@ func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runti | ||||
|  | ||||
| // PatchResource returns a function that will handle a resource patch | ||||
| // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner | ||||
| func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, converter runtime.ObjectConvertor) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// TODO: we either want to remove timeout or document it (if we | ||||
| 		// document, move timeout out of this function and declare it in | ||||
| 		// api_installer) | ||||
| 		timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) | ||||
| 		timeout := parseTimeout(req.URL.Query().Get("timeout")) | ||||
|  | ||||
| 		namespace, name, err := scope.Namer.Name(req.Request) | ||||
| 		namespace, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion()) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// TODO: handle this in negotiation | ||||
| 		contentType := req.HeaderParameter("Content-Type") | ||||
| 		contentType := req.Header.Get("Content-Type") | ||||
| 		// Remove "; charset=" if included in header. | ||||
| 		if idx := strings.Index(contentType, ";"); idx > 0 { | ||||
| 			contentType = contentType[:idx] | ||||
| 		} | ||||
| 		patchType := types.PatchType(contentType) | ||||
|  | ||||
| 		patchJS, err := readBody(req.Request) | ||||
| 		patchJS, err := readBody(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON) | ||||
| 		if !ok { | ||||
| 			scope.err(fmt.Errorf("no serializer defined for JSON"), res.ResponseWriter, req.Request) | ||||
| 			scope.err(fmt.Errorf("no serializer defined for JSON"), w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		gv := scope.Kind.GroupVersion() | ||||
| @@ -504,16 +502,16 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface | ||||
| 		result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS, | ||||
| 			scope.Namer, scope.Copier, scope.Creater, scope.Defaulter, scope.UnsafeConvertor, scope.Kind, scope.Resource, codec) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if err := setSelfLink(result, req, scope.Namer); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -755,34 +753,32 @@ func patchResource( | ||||
| } | ||||
|  | ||||
| // UpdateResource returns a function that will handle a resource update | ||||
| func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// For performance tracking purposes. | ||||
| 		trace := utiltrace.New("Update " + req.Request.URL.Path) | ||||
| 		trace := utiltrace.New("Update " + req.URL.Path) | ||||
| 		defer trace.LogIfLong(500 * time.Millisecond) | ||||
|  | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| 		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) | ||||
| 		timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) | ||||
| 		timeout := parseTimeout(req.URL.Query().Get("timeout")) | ||||
|  | ||||
| 		namespace, name, err := scope.Namer.Name(req.Request) | ||||
| 		namespace, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		body, err := readBody(req.Request) | ||||
| 		body, err := readBody(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer) | ||||
| 		s, err := negotiation.NegotiateInputSerializer(req, scope.Serializer) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		defaultGVK := scope.Kind | ||||
| @@ -791,18 +787,18 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType | ||||
| 		obj, gvk, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original) | ||||
| 		if err != nil { | ||||
| 			err = transformDecodeError(typer, err, original, gvk, body) | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		if gvk.GroupVersion() != defaultGVK.GroupVersion() { | ||||
| 			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion())) | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Conversion done") | ||||
|  | ||||
| 		if err := checkName(obj, name, namespace, scope.Namer); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -822,13 +818,13 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType | ||||
| 			return obj, err | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Object stored in database") | ||||
|  | ||||
| 		if err := setSelfLink(result, req, scope.Namer); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Self-link added") | ||||
| @@ -837,41 +833,39 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType | ||||
| 		if wasCreated { | ||||
| 			status = http.StatusCreated | ||||
| 		} | ||||
| 		responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeleteResource returns a function that will handle a resource deletion | ||||
| func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// For performance tracking purposes. | ||||
| 		trace := utiltrace.New("Delete " + req.Request.URL.Path) | ||||
| 		trace := utiltrace.New("Delete " + req.URL.Path) | ||||
| 		defer trace.LogIfLong(500 * time.Millisecond) | ||||
|  | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| 		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) | ||||
| 		timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) | ||||
| 		timeout := parseTimeout(req.URL.Query().Get("timeout")) | ||||
|  | ||||
| 		namespace, name, err := scope.Namer.Name(req.Request) | ||||
| 		namespace, name, err := scope.Namer.Name(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		options := &metav1.DeleteOptions{} | ||||
| 		if allowsOptions { | ||||
| 			body, err := readBody(req.Request) | ||||
| 			body, err := readBody(req) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 			if len(body) > 0 { | ||||
| 				s, err := negotiation.NegotiateInputSerializer(req.Request, metainternalversion.Codecs) | ||||
| 				s, err := negotiation.NegotiateInputSerializer(req, metainternalversion.Codecs) | ||||
| 				if err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 				// For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions | ||||
| @@ -879,17 +873,17 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco | ||||
| 				defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions") | ||||
| 				obj, _, err := metainternalversion.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) | ||||
| 				if err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 				if obj != options { | ||||
| 					scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) | ||||
| 					scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) | ||||
| 					return | ||||
| 				} | ||||
| 			} else { | ||||
| 				if values := req.Request.URL.Query(); len(values) > 0 { | ||||
| 				if values := req.URL.Query(); len(values) > 0 { | ||||
| 					if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil { | ||||
| 						scope.err(err, res.ResponseWriter, req.Request) | ||||
| 						scope.err(err, w, req) | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| @@ -901,7 +895,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco | ||||
|  | ||||
| 			err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| @@ -914,7 +908,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco | ||||
| 			return obj, err | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
| 		trace.Step("Object deleted from database") | ||||
| @@ -944,30 +938,28 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco | ||||
| 			// when a non-status response is returned, set the self link | ||||
| 			if _, ok := result.(*metav1.Status); !ok { | ||||
| 				if err := setSelfLink(result, req, scope.Namer); err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) | ||||
| 		responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeleteCollection returns a function that will handle a collection deletion | ||||
| func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		w := res.ResponseWriter | ||||
|  | ||||
| func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) | ||||
| 		timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) | ||||
| 		timeout := parseTimeout(req.URL.Query().Get("timeout")) | ||||
|  | ||||
| 		namespace, err := scope.Namer.Namespace(req.Request) | ||||
| 		namespace, err := scope.Namer.Namespace(req) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx := scope.ContextFunc(req.Request) | ||||
| 		ctx := scope.ContextFunc(req) | ||||
| 		ctx = request.WithNamespace(ctx, namespace) | ||||
|  | ||||
| 		if admit != nil && admit.Handles(admission.Delete) { | ||||
| @@ -975,14 +967,14 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco | ||||
|  | ||||
| 			err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		listOptions := metainternalversion.ListOptions{} | ||||
| 		if err := metainternalversion.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 		if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil { | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -995,32 +987,32 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco | ||||
| 			if listOptions.FieldSelector, err = listOptions.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) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		options := &metav1.DeleteOptions{} | ||||
| 		if checkBody { | ||||
| 			body, err := readBody(req.Request) | ||||
| 			body, err := readBody(req) | ||||
| 			if err != nil { | ||||
| 				scope.err(err, res.ResponseWriter, req.Request) | ||||
| 				scope.err(err, w, req) | ||||
| 				return | ||||
| 			} | ||||
| 			if len(body) > 0 { | ||||
| 				s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer) | ||||
| 				s, err := negotiation.NegotiateInputSerializer(req, scope.Serializer) | ||||
| 				if err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 				defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") | ||||
| 				obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) | ||||
| 				if err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 				if obj != options { | ||||
| 					scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) | ||||
| 					scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| @@ -1030,7 +1022,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco | ||||
| 			return r.DeleteCollection(ctx, options, &listOptions) | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			scope.err(err, res.ResponseWriter, req.Request) | ||||
| 			scope.err(err, w, req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| @@ -1048,12 +1040,12 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco | ||||
| 			// when a non-status response is returned, set the self link | ||||
| 			if _, ok := result.(*metav1.Status); !ok { | ||||
| 				if _, err := setListSelfLink(result, req, scope.Namer); err != nil { | ||||
| 					scope.err(err, res.ResponseWriter, req.Request) | ||||
| 					scope.err(err, w, req) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		responsewriters.WriteObjectNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result) | ||||
| 		responsewriters.WriteObjectNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req, http.StatusOK, result) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1113,9 +1105,9 @@ func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime | ||||
|  | ||||
| // setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request | ||||
| // plus the path and query generated by the provided linkFunc | ||||
| func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error { | ||||
| func setSelfLink(obj runtime.Object, req *http.Request, namer ScopeNamer) error { | ||||
| 	// TODO: SelfLink generation should return a full URL? | ||||
| 	uri, err := namer.GenerateLink(req.Request, obj) | ||||
| 	uri, err := namer.GenerateLink(req, obj) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -1160,12 +1152,12 @@ func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) err | ||||
|  | ||||
| // setListSelfLink sets the self link of a list to the base URL, then sets the self links | ||||
| // on all child objects returned. Returns the number of items in the list. | ||||
| func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) (int, error) { | ||||
| func setListSelfLink(obj runtime.Object, req *http.Request, namer ScopeNamer) (int, error) { | ||||
| 	if !meta.IsListType(obj) { | ||||
| 		return 0, nil | ||||
| 	} | ||||
|  | ||||
| 	uri, err := namer.GenerateListLink(req.Request) | ||||
| 	uri, err := namer.GenerateListLink(req) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
|   | ||||
| @@ -33,7 +33,6 @@ import ( | ||||
| 	"k8s.io/apiserver/pkg/server/httplog" | ||||
| 	"k8s.io/apiserver/pkg/util/wsstream" | ||||
|  | ||||
| 	"github.com/emicklei/go-restful" | ||||
| 	"golang.org/x/net/websocket" | ||||
| ) | ||||
|  | ||||
| @@ -62,18 +61,18 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { | ||||
|  | ||||
| // serveWatch handles serving requests to the server | ||||
| // TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. | ||||
| func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) { | ||||
| func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request, w http.ResponseWriter, timeout time.Duration) { | ||||
| 	// negotiate for the stream serializer | ||||
| 	serializer, err := negotiation.NegotiateOutputStreamSerializer(req.Request, scope.Serializer) | ||||
| 	serializer, err := negotiation.NegotiateOutputStreamSerializer(req, scope.Serializer) | ||||
| 	if err != nil { | ||||
| 		scope.err(err, res.ResponseWriter, req.Request) | ||||
| 		scope.err(err, w, req) | ||||
| 		return | ||||
| 	} | ||||
| 	framer := serializer.StreamSerializer.Framer | ||||
| 	streamSerializer := serializer.StreamSerializer.Serializer | ||||
| 	embedded := serializer.Serializer | ||||
| 	if framer == nil { | ||||
| 		scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), res.ResponseWriter, req.Request) | ||||
| 		scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), w, req) | ||||
| 		return | ||||
| 	} | ||||
| 	encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion()) | ||||
| @@ -107,7 +106,7 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Reques | ||||
| 		TimeoutFactory: &realTimeoutFactory{timeout}, | ||||
| 	} | ||||
|  | ||||
| 	server.ServeHTTP(res.ResponseWriter, req.Request) | ||||
| 	server.ServeHTTP(w, req) | ||||
| } | ||||
|  | ||||
| // WatchServer serves a watch.Interface over a websocket or vanilla HTTP. | ||||
|   | ||||
| @@ -32,6 +32,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apiserver/pkg/admission" | ||||
| 	"k8s.io/apiserver/pkg/endpoints/handlers" | ||||
| 	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation" | ||||
| 	"k8s.io/apiserver/pkg/endpoints/metrics" | ||||
| @@ -560,9 +561,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 		case "GET": // Get a resource. | ||||
| 			var handler restful.RouteFunction | ||||
| 			if isGetterWithOptions { | ||||
| 				handler = handlers.GetResourceWithOptions(getterWithOptions, reqScope) | ||||
| 				handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, hasSubresource) | ||||
| 			} else { | ||||
| 				handler = handlers.GetResource(getter, exporter, reqScope) | ||||
| 				handler = restfulGetResource(getter, exporter, reqScope) | ||||
| 			} | ||||
| 			handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) | ||||
| 			doc := "read the specified " + kind | ||||
| @@ -593,7 +594,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "list " + subresource + " of objects of kind " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, false, a.minRequestTimeout)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout)) | ||||
| 			route := ws.GET(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -625,7 +626,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "replace " + subresource + " of the specified " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.UpdateResource(updater, reqScope, a.group.Typer, admit)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulUpdateResource(updater, reqScope, a.group.Typer, admit)) | ||||
| 			route := ws.PUT(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -641,7 +642,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "partially update " + subresource + " of the specified " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.PatchResource(patcher, reqScope, admit, mapping.ObjectConvertor)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulPatchResource(patcher, reqScope, admit, mapping.ObjectConvertor)) | ||||
| 			route := ws.PATCH(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -656,9 +657,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 		case "POST": // Create a resource. | ||||
| 			var handler restful.RouteFunction | ||||
| 			if isNamedCreater { | ||||
| 				handler = handlers.CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit) | ||||
| 				handler = restfulCreateNamedResource(namedCreater, reqScope, a.group.Typer, admit) | ||||
| 			} else { | ||||
| 				handler = handlers.CreateResource(creater, reqScope, a.group.Typer, admit) | ||||
| 				handler = restfulCreateResource(creater, reqScope, a.group.Typer, admit) | ||||
| 			} | ||||
| 			handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) | ||||
| 			article := getArticleForNoun(kind, " ") | ||||
| @@ -682,7 +683,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "delete " + subresource + " of" + article + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)) | ||||
| 			route := ws.DELETE(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -703,7 +704,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "delete collection of " + subresource + " of a " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.DeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit)) | ||||
| 			route := ws.DELETE(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -722,7 +723,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "watch changes to " + subresource + " of an object of kind " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) | ||||
| 			route := ws.GET(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -741,7 +742,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 			if hasSubresource { | ||||
| 				doc = "watch individual changes to a list of " + subresource + " of " + kind | ||||
| 			} | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) | ||||
| 			handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) | ||||
| 			route := ws.GET(action.Path).To(handler). | ||||
| 				Doc(doc). | ||||
| 				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). | ||||
| @@ -772,7 +773,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | ||||
| 				if hasSubresource { | ||||
| 					doc = "connect " + method + " requests to " + subresource + " of " + kind | ||||
| 				} | ||||
| 				handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ConnectResource(connecter, reqScope, admit, path)) | ||||
| 				handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulConnectResource(connecter, reqScope, admit, path, hasSubresource)) | ||||
| 				route := ws.Method(method).Path(action.Path). | ||||
| 					To(handler). | ||||
| 					Doc(doc). | ||||
| @@ -979,3 +980,63 @@ func isVowel(c rune) bool { | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulCreateNamedResource(r rest.NamedCreater, scope handlers.RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.CreateNamedResource(r, scope, typer, admit)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.CreateResource(r, scope, typer, admit)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulDeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.DeleteResource(r, allowsOptions, scope, admit)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulDeleteCollection(r rest.CollectionDeleter, checkBody bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.DeleteCollection(r, checkBody, scope, admit)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulUpdateResource(r rest.Updater, scope handlers.RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.UpdateResource(r, scope, typer, admit)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.PatchResource(r, scope, admit, converter)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulGetResource(r rest.Getter, e rest.Exporter, scope handlers.RequestScope) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.GetResource(r, e, scope)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulGetResourceWithOptions(r rest.GetterWithOptions, scope handlers.RequestScope, isSubresource bool) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.GetResourceWithOptions(r, scope, isSubresource)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func restfulConnectResource(connecter rest.Connecter, scope handlers.RequestScope, admit admission.Interface, restPath string, isSubresource bool) restful.RouteFunction { | ||||
| 	return func(req *restful.Request, res *restful.Response) { | ||||
| 		handlers.ConnectResource(connecter, scope, admit, restPath, isSubresource)(res.ResponseWriter, req.Request) | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Submit Queue
					Kubernetes Submit Queue