mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
remove go-restful from namer for rest handling
This commit is contained in:
parent
19b8be8b7d
commit
da27957390
@ -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)
|
||||
}
|
||||
|
147
staging/src/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
Normal file
147
staging/src/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
Normal 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")
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
3
vendor/BUILD
vendored
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user