remove go-restful from namer for rest handling

This commit is contained in:
deads2k 2017-03-28 14:49:44 -04:00
parent 19b8be8b7d
commit da27957390
7 changed files with 256 additions and 288 deletions

View File

@ -26,6 +26,7 @@ import (
"math/rand"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"reflect"
"strconv"
@ -331,7 +332,17 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
}
}
return &defaultAPIServer{mux, container}
handler := genericapifilters.WithRequestInfo(mux, testRequestInfoResolver(), requestContextMapper)
handler = request.WithRequestContext(handler, requestContextMapper)
return &defaultAPIServer{handler, container}
}
func testRequestInfoResolver() *request.RequestInfoFactory {
return &request.RequestInfoFactory{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
}
}
func TestSimpleSetupRight(t *testing.T) {
@ -746,7 +757,7 @@ func TestNotFound(t *testing.T) {
"groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
"groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound},
"groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},
"groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
@ -757,7 +768,7 @@ func TestNotFound(t *testing.T) {
"groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound},
"groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError},
"groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
"groupless namespaced watch param with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},
@ -777,7 +788,7 @@ func TestNotFound(t *testing.T) {
"root DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"root PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
"root PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"root watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusNotFound},
"root watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
// TODO: JTL: "root watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroot/bar", http.StatusMethodNotAllowed},
"namespaced PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
@ -789,7 +800,7 @@ func TestNotFound(t *testing.T) {
"namespaced DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"namespaced PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"namespaced PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusNotFound},
"namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError},
"namespaced watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
"namespaced watch param with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed},
}
@ -1081,7 +1092,7 @@ func TestList(t *testing.T) {
if !simpleStorage.namespacePresent {
t.Errorf("%d: namespace not set", i)
} else if simpleStorage.actualNamespace != testCase.namespace {
t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace)
t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
}
if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector)
@ -1169,6 +1180,7 @@ func TestNonEmptyList(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Log(body)
if len(listOut.Items) != 1 {
t.Errorf("Unexpected response: %#v", listOut)
@ -2220,10 +2232,12 @@ func TestPatch(t *testing.T) {
client := http.Client{}
request, err := http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`)))
request.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8")
_, err = client.Do(request)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
if simpleStorage.updated == nil || simpleStorage.updated.Labels["foo"] != "bar" {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
@ -2292,10 +2306,12 @@ func TestUpdate(t *testing.T) {
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
@ -2333,6 +2349,9 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
@ -2343,7 +2362,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := handleDeny(storage)
handler := handle(storage)
server := httptest.NewServer(handler)
defer server.Close()
@ -2363,6 +2382,8 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusBadRequest {
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
t.Errorf("Unexpected response %#v", response)
}
}
@ -2394,13 +2415,16 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
if response.StatusCode != http.StatusOK {
t.Errorf("Unexpected response %#v", response)
}
}
// when the object name and namespace can't be retrieved, skip name checking
func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
// when the object name and namespace can't be retrieved, don't update. It isn't safe.
func TestUpdateDisallowsMismatchedNamespaceOnError(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
@ -2428,13 +2452,15 @@ func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
_, err = client.Do(request)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
if simpleStorage.updated != nil {
t.Errorf("Unexpected update value %#v.", simpleStorage.updated)
}
if selfLinker.called {
t.Errorf("self link ignored")
@ -2605,14 +2631,17 @@ func TestUpdateREST(t *testing.T) {
}
testREST := func(t *testing.T, container *restful.Container, barCode int) {
handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver(), requestContextMapper)
handler = request.WithRequestContext(handler, requestContextMapper)
w := httptest.NewRecorder()
container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/foo/test"}})
handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/foo/test"}})
if w.Code != http.StatusOK {
t.Fatalf("expected OK: %#v", w)
}
w = httptest.NewRecorder()
container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/bar/test"}})
handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/bar/test"}})
if w.Code != barCode {
t.Errorf("expected response code %d for GET to bar but received %d", barCode, w.Code)
}
@ -2716,16 +2745,19 @@ func TestParentResourceIsRequired(t *testing.T) {
t.Fatal(err)
}
handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver(), requestContextMapper)
handler = request.WithRequestContext(handler, requestContextMapper)
// resource is NOT registered in the root scope
w := httptest.NewRecorder()
container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}})
handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}})
if w.Code != http.StatusNotFound {
t.Errorf("expected not found: %#v", w)
}
// resource is registered in the namespace scope
w = httptest.NewRecorder()
container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}})
handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}})
if w.Code != http.StatusOK {
t.Fatalf("expected OK: %#v", w)
}

View File

@ -0,0 +1,147 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"fmt"
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
)
// ContextFunc returns a Context given a request - a context must be returned
type ContextFunc func(req *http.Request) request.Context
// ScopeNamer handles accessing names from requests and objects
type ScopeNamer interface {
// Namespace returns the appropriate namespace value from the request (may be empty) or an
// error.
Namespace(req *http.Request) (namespace string, err error)
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
Name(req *http.Request) (namespace, name string, err error)
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
// does not support names.
ObjectName(obj runtime.Object) (namespace, name string, err error)
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
// does not support selfLinks.
SetSelfLink(obj runtime.Object, url string) error
// GenerateLink creates an encoded URI for a given runtime object that represents the canonical path
// and query.
GenerateLink(req *http.Request, obj runtime.Object) (uri string, err error)
// GenerateLink creates an encoded URI for a list that represents the canonical path and query.
GenerateListLink(req *http.Request) (uri string, err error)
}
type ContextBasedNaming struct {
GetContext ContextFunc
SelfLinker runtime.SelfLinker
ClusterScoped bool
SelfLinkPathPrefix string
SelfLinkPathSuffix string
}
// ContextBasedNaming implements ScopeNamer
var _ ScopeNamer = ContextBasedNaming{}
func (n ContextBasedNaming) SetSelfLink(obj runtime.Object, url string) error {
return n.SelfLinker.SetSelfLink(obj, url)
}
func (n ContextBasedNaming) Namespace(req *http.Request) (namespace string, err error) {
requestInfo, ok := request.RequestInfoFrom(n.GetContext(req))
if !ok {
return "", fmt.Errorf("missing requestInfo")
}
return requestInfo.Namespace, nil
}
func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err error) {
requestInfo, ok := request.RequestInfoFrom(n.GetContext(req))
if !ok {
return "", "", fmt.Errorf("missing requestInfo")
}
ns, err := n.Namespace(req)
if err != nil {
return "", "", err
}
if len(requestInfo.Name) == 0 {
return "", "", errEmptyName
}
return ns, requestInfo.Name, nil
}
func (n ContextBasedNaming) GenerateLink(req *http.Request, obj runtime.Object) (uri string, err error) {
namespace, name, err := n.ObjectName(obj)
if err != nil {
return "", err
}
requestInfo, ok := request.RequestInfoFrom(n.GetContext(req))
if !ok {
return "", fmt.Errorf("missing requestInfo")
}
if len(namespace) == 0 && len(name) == 0 {
if len(requestInfo.Name) == 0 {
return "", errEmptyName
}
namespace = requestInfo.Namespace
name = requestInfo.Name
}
if n.ClusterScoped {
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
}
return n.SelfLinkPathPrefix +
url.QueryEscape(namespace) +
"/" + url.QueryEscape(requestInfo.Resource) + "/" +
url.QueryEscape(name) +
n.SelfLinkPathSuffix,
nil
}
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
if len(req.URL.RawPath) > 0 {
return req.URL.RawPath, nil
}
return req.URL.EscapedPath(), nil
}
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
if len(name) == 0 {
return "", "", errEmptyName
}
namespace, err = n.SelfLinker.Namespace(obj)
if err != nil {
return "", "", err
}
return namespace, name, err
}
// errEmptyName is returned when API requests do not fill the name section of the path.
var errEmptyName = errors.NewBadRequest("name must be provided")

View File

@ -50,30 +50,6 @@ import (
utiltrace "k8s.io/apiserver/pkg/util/trace"
)
// ContextFunc returns a Context given a request - a context must be returned
type ContextFunc func(req *restful.Request) request.Context
// ScopeNamer handles accessing names from requests and objects
type ScopeNamer interface {
// Namespace returns the appropriate namespace value from the request (may be empty) or an
// error.
Namespace(req *restful.Request) (namespace string, err error)
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
Name(req *restful.Request) (namespace, name string, err error)
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
// does not support names.
ObjectName(obj runtime.Object) (namespace, name string, err error)
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
// does not support selfLinks.
SetSelfLink(obj runtime.Object, url string) error
// GenerateLink creates an encoded URI for a given runtime object that represents the canonical path
// and query.
GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error)
// GenerateLink creates an encoded URI for a list that represents the canonical path and query.
GenerateListLink(req *restful.Request) (uri string, err error)
}
// RequestScope encapsulates common fields across all RESTful handler methods.
type RequestScope struct {
Namer ScopeNamer
@ -112,12 +88,12 @@ const MaxRetryWhenPatchConflicts = 5
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)
namespace, name, err := scope.Namer.Name(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
result, err := getter(ctx, name, req)
@ -196,12 +172,12 @@ func getRequestOptions(req *restful.Request, scope RequestScope, into runtime.Ob
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)
namespace, name, err := scope.Namer.Name(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
opts, subpath, subpathKey := connecter.NewConnectOptions()
if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil {
@ -254,7 +230,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
w := res.ResponseWriter
namespace, err := scope.Namer.Namespace(req)
namespace, err := scope.Namer.Namespace(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
@ -263,12 +239,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
// 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)
_, name, err := scope.Namer.Name(req.Request)
if err != nil {
hasName = false
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
opts := metainternalversion.ListOptions{}
@ -371,16 +347,16 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
err error
)
if includeName {
namespace, name, err = scope.Namer.Name(req)
namespace, name, err = scope.Namer.Name(req.Request)
} else {
namespace, err = scope.Namer.Namespace(req)
namespace, err = scope.Namer.Namespace(req.Request)
}
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
gv := scope.Kind.GroupVersion()
@ -476,13 +452,13 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
// api_installer)
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
namespace, name, err := scope.Namer.Name(req)
namespace, name, err := scope.Namer.Name(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion())
@ -790,12 +766,12 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
// 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"))
namespace, name, err := scope.Namer.Name(req)
namespace, name, err := scope.Namer.Name(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
body, err := readBody(req.Request)
@ -877,12 +853,12 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
// 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"))
namespace, name, err := scope.Namer.Name(req)
namespace, name, err := scope.Namer.Name(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
options := &metav1.DeleteOptions{}
@ -985,13 +961,13 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
// 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"))
namespace, err := scope.Namer.Namespace(req)
namespace, err := scope.Namer.Namespace(req.Request)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
}
ctx := scope.ContextFunc(req)
ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace)
if admit != nil && admit.Handles(admission.Delete) {
@ -1139,7 +1115,7 @@ func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime
// plus the path and query generated by the provided linkFunc
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
// TODO: SelfLink generation should return a full URL?
uri, err := namer.GenerateLink(req, obj)
uri, err := namer.GenerateLink(req.Request, obj)
if err != nil {
return nil
}
@ -1163,21 +1139,22 @@ func hasUID(obj runtime.Object) (bool, error) {
// checkName checks the provided name against the request
func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error {
if objNamespace, objName, err := namer.ObjectName(obj); err == nil {
if err != nil {
return err
}
if objName != name {
objNamespace, objName, err := namer.ObjectName(obj)
if err != nil {
return errors.NewBadRequest(fmt.Sprintf(
"the name of the object (%s based on URL) was undeterminable: %v", name, err))
}
if objName != name {
return errors.NewBadRequest(fmt.Sprintf(
"the name of the object (%s) does not match the name on the URL (%s)", objName, name))
}
if len(namespace) > 0 {
if len(objNamespace) > 0 && objNamespace != namespace {
return errors.NewBadRequest(fmt.Sprintf(
"the name of the object (%s) does not match the name on the URL (%s)", objName, name))
}
if len(namespace) > 0 {
if len(objNamespace) > 0 && objNamespace != namespace {
return errors.NewBadRequest(fmt.Sprintf(
"the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace))
}
"the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace))
}
}
return nil
}
@ -1188,7 +1165,7 @@ func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer)
return 0, nil
}
uri, err := namer.GenerateListLink(req)
uri, err := namer.GenerateListLink(req.Request)
if err != nil {
return 0, err
}

View File

@ -19,11 +19,11 @@ package handlers
import (
"errors"
"fmt"
"net/http"
"reflect"
"testing"
"time"
"github.com/emicklei/go-restful"
"github.com/evanphx/json-patch"
apiequality "k8s.io/apimachinery/pkg/api/equality"
@ -128,13 +128,13 @@ type testNamer struct {
name string
}
func (p *testNamer) Namespace(req *restful.Request) (namespace string, err error) {
func (p *testNamer) Namespace(req *http.Request) (namespace string, err error) {
return p.namespace, nil
}
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
func (p *testNamer) Name(req *restful.Request) (namespace, name string, err error) {
func (p *testNamer) Name(req *http.Request) (namespace, name string, err error) {
return p.namespace, p.name, nil
}
@ -151,12 +151,12 @@ func (p *testNamer) SetSelfLink(obj runtime.Object, url string) error {
}
// GenerateLink creates a path and query for a given runtime object that represents the canonical path.
func (p *testNamer) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) {
func (p *testNamer) GenerateLink(req *http.Request, obj runtime.Object) (uri string, err error) {
return "", errors.New("not implemented")
}
// GenerateLink creates a path and query for a list that represents the canonical path.
func (p *testNamer) GenerateListLink(req *restful.Request) (uri string, err error) {
func (p *testNamer) GenerateListLink(req *http.Request) (uri string, err error) {
return "", errors.New("not implemented")
}

View File

@ -17,10 +17,8 @@ limitations under the License.
package endpoints
import (
"bytes"
"fmt"
"net/http"
"net/url"
gpath "path"
"reflect"
"sort"
@ -28,7 +26,6 @@ import (
"time"
"unicode"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
@ -79,9 +76,6 @@ var toDiscoveryKubeVerb = map[string]string{
"WATCHLIST": "watch",
}
// errEmptyName is returned when API requests do not fill the name section of the path.
var errEmptyName = errors.NewBadRequest("name must be provided")
// Installs handlers for API resources.
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []metav1.APIResource, errors []error) {
errors = make([]error, 0)
@ -191,6 +185,9 @@ func (a *APIInstaller) restMapping(resource string) (*meta.RESTMapping, error) {
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*metav1.APIResource, error) {
admit := a.group.Admit
context := a.group.Context
if context == nil {
return nil, fmt.Errorf("%v missing Context", a.group.GroupVersion)
}
optionsExternalVersion := a.group.GroupVersion
if a.group.OptionsExternalVersion != nil {
@ -342,14 +339,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
}
var ctxFn handlers.ContextFunc
ctxFn = func(req *restful.Request) request.Context {
if context == nil {
return request.WithUserAgent(request.NewContext(), req.HeaderParameter("User-Agent"))
ctxFn = func(req *http.Request) request.Context {
if ctx, ok := context.Get(req); ok {
return request.WithUserAgent(ctx, req.Header.Get("User-Agent"))
}
if ctx, ok := context.Get(req.Request); ok {
return request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
}
return request.WithUserAgent(request.NewContext(), req.HeaderParameter("User-Agent"))
return request.WithUserAgent(request.NewContext(), req.Header.Get("User-Agent"))
}
allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list.
@ -394,7 +388,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
apiResource.Name = path
apiResource.Namespaced = false
apiResource.Kind = resourceKind
namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, resourcePath, "/"), suffix}
namer := handlers.ContextBasedNaming{
GetContext: ctxFn,
SelfLinker: a.group.Linker,
ClusterScoped: true,
SelfLinkPathPrefix: gpath.Join(a.prefix, resourcePath, "/"),
SelfLinkPathSuffix: suffix,
}
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
// Add actions at the resource path: /api/apiVersion/resource
@ -430,9 +430,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
resourcePath := namespacedPath
resourceParams := namespaceParams
itemPathPrefix := gpath.Join(a.prefix, scope.ParamName()) + "/"
itemPath := namespacedPath + "/{name}"
itemPathMiddle := "/" + resource + "/"
nameParams := append(namespaceParams, nameParam)
proxyParams := append(nameParams, pathParam)
itemPathSuffix := ""
@ -445,17 +443,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
apiResource.Name = path
apiResource.Namespaced = true
apiResource.Kind = resourceKind
itemPathFn := func(name, namespace string) bytes.Buffer {
var buf bytes.Buffer
buf.WriteString(itemPathPrefix)
buf.WriteString(url.QueryEscape(namespace))
buf.WriteString(itemPathMiddle)
buf.WriteString(url.QueryEscape(name))
buf.WriteString(itemPathSuffix)
return buf
namer := handlers.ContextBasedNaming{
GetContext: ctxFn,
SelfLinker: a.group.Linker,
ClusterScoped: false,
SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/",
SelfLinkPathSuffix: itemPathSuffix,
}
namer := scopeNaming{scope, a.group.Linker, itemPathFn, false}
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
@ -484,7 +478,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
// For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods.
// TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
if !hasSubresource {
namer = scopeNaming{scope, a.group.Linker, itemPathFn, true}
actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList)
}
@ -811,149 +804,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
return &apiResource, nil
}
// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer
// for root scoped resources.
type rootScopeNaming struct {
scope meta.RESTScope
runtime.SelfLinker
pathPrefix string
pathSuffix string
}
// rootScopeNaming implements ScopeNamer
var _ handlers.ScopeNamer = rootScopeNaming{}
// Namespace returns an empty string because root scoped objects have no namespace.
func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
return "", nil
}
// Name returns the name from the path and an empty string for namespace, or an error if the
// name is empty.
func (n rootScopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
name = req.PathParameter("name")
if len(name) == 0 {
return "", "", errEmptyName
}
return "", name, nil
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func (n rootScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) {
_, name, err := n.ObjectName(obj)
if err != nil {
return "", err
}
if len(name) == 0 {
_, name, err = n.Name(req)
if err != nil {
return "", err
}
}
return n.pathPrefix + url.QueryEscape(name) + n.pathSuffix, nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func (n rootScopeNaming) GenerateListLink(req *restful.Request) (uri string, err error) {
if len(req.Request.URL.RawPath) > 0 {
return req.Request.URL.RawPath, nil
}
return req.Request.URL.EscapedPath(), nil
}
// ObjectName returns the name set on the object, or an error if the
// name cannot be returned. Namespace is empty
// TODO: distinguish between objects with name/namespace and without via a specific error.
func (n rootScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
if len(name) == 0 {
return "", "", errEmptyName
}
return "", name, nil
}
// scopeNaming returns naming information from a request. It implements ScopeNamer for
// namespace scoped resources.
type scopeNaming struct {
scope meta.RESTScope
runtime.SelfLinker
itemPathFn func(name, namespace string) bytes.Buffer
allNamespaces bool
}
// scopeNaming implements ScopeNamer
var _ handlers.ScopeNamer = scopeNaming{}
// Namespace returns the namespace from the path or the default.
func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
if n.allNamespaces {
return "", nil
}
namespace = req.PathParameter(n.scope.ArgumentName())
if len(namespace) == 0 {
// a URL was constructed without the namespace, or this method was invoked
// on an object without a namespace path parameter.
return "", fmt.Errorf("no namespace parameter found on request")
}
return namespace, nil
}
// Name returns the name from the path, the namespace (or default), or an error if the
// name is empty.
func (n scopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
namespace, _ = n.Namespace(req)
name = req.PathParameter("name")
if len(name) == 0 {
return "", "", errEmptyName
}
return
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func (n scopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) {
namespace, name, err := n.ObjectName(obj)
if err != nil {
return "", err
}
if len(namespace) == 0 && len(name) == 0 {
namespace, name, err = n.Name(req)
if err != nil {
return "", err
}
}
if len(name) == 0 {
return "", errEmptyName
}
result := n.itemPathFn(name, namespace)
return result.String(), nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func (n scopeNaming) GenerateListLink(req *restful.Request) (uri string, err error) {
if len(req.Request.URL.RawPath) > 0 {
return req.Request.URL.RawPath, nil
}
return req.Request.URL.EscapedPath(), nil
}
// ObjectName returns the name and namespace set on the object, or an error if the
// name cannot be returned.
// TODO: distinguish between objects with name/namespace and without via a specific error.
func (n scopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
name, err = n.SelfLinker.Name(obj)
if err != nil {
return "", "", err
}
namespace, err = n.SelfLinker.Namespace(obj)
if err != nil {
return "", "", err
}
return namespace, name, err
}
// This magic incantation returns *ptrToObject for an arbitrary pointer
func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()

View File

@ -17,46 +17,9 @@ limitations under the License.
package endpoints
import (
"bytes"
"testing"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/api"
"github.com/emicklei/go-restful"
)
func TestScopeNamingGenerateLink(t *testing.T) {
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/api/v1/namespaces/other/services/foo",
name: "foo",
namespace: "other",
}
s := scopeNaming{
meta.RESTScopeNamespace,
selfLinker,
func(name, namespace string) bytes.Buffer {
return *bytes.NewBufferString("/api/v1/namespaces/" + namespace + "/services/" + name)
},
true,
}
service := &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "other",
},
TypeMeta: metav1.TypeMeta{
Kind: "Service",
},
}
_, err := s.GenerateLink(&restful.Request{}, service)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
func TestIsVowel(t *testing.T) {
tests := []struct {
name string

3
vendor/BUILD vendored
View File

@ -9950,7 +9950,6 @@ go_test(
"//vendor:k8s.io/apiserver/pkg/endpoints/request",
"//vendor:k8s.io/apiserver/pkg/endpoints/testing",
"//vendor:k8s.io/apiserver/pkg/registry/rest",
"//vendor:k8s.io/client-go/pkg/api",
],
)
@ -10041,7 +10040,6 @@ go_test(
library = ":k8s.io/apiserver/pkg/endpoints/handlers",
tags = ["automanaged"],
deps = [
"//vendor:github.com/emicklei/go-restful",
"//vendor:github.com/evanphx/json-patch",
"//vendor:k8s.io/apimachinery/pkg/api/equality",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
@ -10064,6 +10062,7 @@ go_library(
srcs = [
"k8s.io/apiserver/pkg/endpoints/handlers/discovery.go",
"k8s.io/apiserver/pkg/endpoints/handlers/doc.go",
"k8s.io/apiserver/pkg/endpoints/handlers/namer.go",
"k8s.io/apiserver/pkg/endpoints/handlers/patch.go",
"k8s.io/apiserver/pkg/endpoints/handlers/proxy.go",
"k8s.io/apiserver/pkg/endpoints/handlers/rest.go",