Commit f5052024 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #43767 from deads2k/server-13-namer

Automatic merge from submit-queue remove go-restful from namer for rest handling Our RESTHandler code is currently tightly coupled to go-restful, but there's no reason for this coupling. It makes integrations that want API handling (decode, sanity check, admission, verb handling), but don't need the REST installer flow impractical. I know of two layers now: metrics and TPR. This starts the process of unwinding by switching the `ScopeNamer` (used for request identification and selflinks) to use the standard http library along with the `RequestInfo` we place in the context for authorization and any other interested layer. @kubernetes/sig-api-machinery-misc @smarterclayton @ncdc @sttts
parents 9a9f622b da279573
/*
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 ( ...@@ -50,30 +50,6 @@ import (
utiltrace "k8s.io/apiserver/pkg/util/trace" 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. // RequestScope encapsulates common fields across all RESTful handler methods.
type RequestScope struct { type RequestScope struct {
Namer ScopeNamer Namer ScopeNamer
...@@ -112,12 +88,12 @@ const MaxRetryWhenPatchConflicts = 5 ...@@ -112,12 +88,12 @@ const MaxRetryWhenPatchConflicts = 5
func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction { func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter w := res.ResponseWriter
namespace, name, err := scope.Namer.Name(req) namespace, name, err := scope.Namer.Name(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
result, err := getter(ctx, name, req) result, err := getter(ctx, name, req)
...@@ -196,12 +172,12 @@ func getRequestOptions(req *restful.Request, scope RequestScope, into runtime.Ob ...@@ -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 { func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
w := res.ResponseWriter w := res.ResponseWriter
namespace, name, err := scope.Namer.Name(req) namespace, name, err := scope.Namer.Name(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
opts, subpath, subpathKey := connecter.NewConnectOptions() opts, subpath, subpathKey := connecter.NewConnectOptions()
if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { 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 ...@@ -254,7 +230,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
w := res.ResponseWriter w := res.ResponseWriter
namespace, err := scope.Namer.Namespace(req) namespace, err := scope.Namer.Namespace(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
...@@ -263,12 +239,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch ...@@ -263,12 +239,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
// Watches for single objects are routed to this function. // Watches for single objects are routed to this function.
// Treat a /name parameter the same as a field selector entry. // Treat a /name parameter the same as a field selector entry.
hasName := true hasName := true
_, name, err := scope.Namer.Name(req) _, name, err := scope.Namer.Name(req.Request)
if err != nil { if err != nil {
hasName = false hasName = false
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
opts := metainternalversion.ListOptions{} opts := metainternalversion.ListOptions{}
...@@ -371,16 +347,16 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object ...@@ -371,16 +347,16 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
err error err error
) )
if includeName { if includeName {
namespace, name, err = scope.Namer.Name(req) namespace, name, err = scope.Namer.Name(req.Request)
} else { } else {
namespace, err = scope.Namer.Namespace(req) namespace, err = scope.Namer.Namespace(req.Request)
} }
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
gv := scope.Kind.GroupVersion() gv := scope.Kind.GroupVersion()
...@@ -476,13 +452,13 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface ...@@ -476,13 +452,13 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
// api_installer) // api_installer)
timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) 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 { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion()) versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion())
...@@ -790,12 +766,12 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType ...@@ -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) // 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.Request.URL.Query().Get("timeout"))
namespace, name, err := scope.Namer.Name(req) namespace, name, err := scope.Namer.Name(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
body, err := readBody(req.Request) body, err := readBody(req.Request)
...@@ -877,12 +853,12 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco ...@@ -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) // 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.Request.URL.Query().Get("timeout"))
namespace, name, err := scope.Namer.Name(req) namespace, name, err := scope.Namer.Name(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
options := &metav1.DeleteOptions{} options := &metav1.DeleteOptions{}
...@@ -985,13 +961,13 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco ...@@ -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) // 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.Request.URL.Query().Get("timeout"))
namespace, err := scope.Namer.Namespace(req) namespace, err := scope.Namer.Namespace(req.Request)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
ctx := scope.ContextFunc(req) ctx := scope.ContextFunc(req.Request)
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
if admit != nil && admit.Handles(admission.Delete) { if admit != nil && admit.Handles(admission.Delete) {
...@@ -1139,7 +1115,7 @@ func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime ...@@ -1139,7 +1115,7 @@ func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime
// plus the path and query generated by the provided linkFunc // 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 *restful.Request, namer ScopeNamer) error {
// TODO: SelfLink generation should return a full URL? // TODO: SelfLink generation should return a full URL?
uri, err := namer.GenerateLink(req, obj) uri, err := namer.GenerateLink(req.Request, obj)
if err != nil { if err != nil {
return nil return nil
} }
...@@ -1163,21 +1139,22 @@ func hasUID(obj runtime.Object) (bool, error) { ...@@ -1163,21 +1139,22 @@ func hasUID(obj runtime.Object) (bool, error) {
// checkName checks the provided name against the request // checkName checks the provided name against the request
func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error { func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error {
if objNamespace, objName, err := namer.ObjectName(obj); err == nil { objNamespace, objName, err := namer.ObjectName(obj)
if err != nil { if err != nil {
return err return errors.NewBadRequest(fmt.Sprintf(
} "the name of the object (%s based on URL) was undeterminable: %v", name, err))
if objName != name { }
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( return errors.NewBadRequest(fmt.Sprintf(
"the name of the object (%s) does not match the name on the URL (%s)", objName, name)) "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace))
}
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))
}
} }
} }
return nil return nil
} }
...@@ -1188,7 +1165,7 @@ func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) ...@@ -1188,7 +1165,7 @@ func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer)
return 0, nil return 0, nil
} }
uri, err := namer.GenerateListLink(req) uri, err := namer.GenerateListLink(req.Request)
if err != nil { if err != nil {
return 0, err return 0, err
} }
......
...@@ -19,11 +19,11 @@ package handlers ...@@ -19,11 +19,11 @@ package handlers
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/emicklei/go-restful"
"github.com/evanphx/json-patch" "github.com/evanphx/json-patch"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
...@@ -128,13 +128,13 @@ type testNamer struct { ...@@ -128,13 +128,13 @@ type testNamer struct {
name string 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 return p.namespace, nil
} }
// Name returns the name from the request, and an optional namespace value if this is a namespace // 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. // 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 return p.namespace, p.name, nil
} }
...@@ -151,12 +151,12 @@ func (p *testNamer) SetSelfLink(obj runtime.Object, url string) error { ...@@ -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. // 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") return "", errors.New("not implemented")
} }
// GenerateLink creates a path and query for a list that represents the canonical path. // 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") return "", errors.New("not implemented")
} }
......
...@@ -17,10 +17,8 @@ limitations under the License. ...@@ -17,10 +17,8 @@ limitations under the License.
package endpoints package endpoints
import ( import (
"bytes"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
gpath "path" gpath "path"
"reflect" "reflect"
"sort" "sort"
...@@ -28,7 +26,6 @@ import ( ...@@ -28,7 +26,6 @@ import (
"time" "time"
"unicode" "unicode"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/conversion"
...@@ -79,9 +76,6 @@ var toDiscoveryKubeVerb = map[string]string{ ...@@ -79,9 +76,6 @@ var toDiscoveryKubeVerb = map[string]string{
"WATCHLIST": "watch", "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. // Installs handlers for API resources.
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []metav1.APIResource, errors []error) { func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []metav1.APIResource, errors []error) {
errors = make([]error, 0) errors = make([]error, 0)
...@@ -191,6 +185,9 @@ func (a *APIInstaller) restMapping(resource string) (*meta.RESTMapping, error) { ...@@ -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) { func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*metav1.APIResource, error) {
admit := a.group.Admit admit := a.group.Admit
context := a.group.Context context := a.group.Context
if context == nil {
return nil, fmt.Errorf("%v missing Context", a.group.GroupVersion)
}
optionsExternalVersion := a.group.GroupVersion optionsExternalVersion := a.group.GroupVersion
if a.group.OptionsExternalVersion != nil { if a.group.OptionsExternalVersion != nil {
...@@ -342,14 +339,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag ...@@ -342,14 +339,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
} }
var ctxFn handlers.ContextFunc var ctxFn handlers.ContextFunc
ctxFn = func(req *restful.Request) request.Context { ctxFn = func(req *http.Request) request.Context {
if context == nil { if ctx, ok := context.Get(req); ok {
return request.WithUserAgent(request.NewContext(), req.HeaderParameter("User-Agent")) return request.WithUserAgent(ctx, req.Header.Get("User-Agent"))
} }
if ctx, ok := context.Get(req.Request); ok { return request.WithUserAgent(request.NewContext(), req.Header.Get("User-Agent"))
return request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
}
return request.WithUserAgent(request.NewContext(), req.HeaderParameter("User-Agent"))
} }
allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list. 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 ...@@ -394,7 +388,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
apiResource.Name = path apiResource.Name = path
apiResource.Namespaced = false apiResource.Namespaced = false
apiResource.Kind = resourceKind 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). // Handler for standard REST verbs (GET, PUT, POST and DELETE).
// Add actions at the resource path: /api/apiVersion/resource // Add actions at the resource path: /api/apiVersion/resource
...@@ -430,9 +430,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag ...@@ -430,9 +430,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
resourcePath := namespacedPath resourcePath := namespacedPath
resourceParams := namespaceParams resourceParams := namespaceParams
itemPathPrefix := gpath.Join(a.prefix, scope.ParamName()) + "/"
itemPath := namespacedPath + "/{name}" itemPath := namespacedPath + "/{name}"
itemPathMiddle := "/" + resource + "/"
nameParams := append(namespaceParams, nameParam) nameParams := append(namespaceParams, nameParam)
proxyParams := append(nameParams, pathParam) proxyParams := append(nameParams, pathParam)
itemPathSuffix := "" itemPathSuffix := ""
...@@ -445,17 +443,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag ...@@ -445,17 +443,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
apiResource.Name = path apiResource.Name = path
apiResource.Namespaced = true apiResource.Namespaced = true
apiResource.Kind = resourceKind apiResource.Kind = resourceKind
namer := handlers.ContextBasedNaming{
itemPathFn := func(name, namespace string) bytes.Buffer { GetContext: ctxFn,
var buf bytes.Buffer SelfLinker: a.group.Linker,
buf.WriteString(itemPathPrefix) ClusterScoped: false,
buf.WriteString(url.QueryEscape(namespace)) SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/",
buf.WriteString(itemPathMiddle) SelfLinkPathSuffix: itemPathSuffix,
buf.WriteString(url.QueryEscape(name))
buf.WriteString(itemPathSuffix)
return buf
} }
namer := scopeNaming{scope, a.group.Linker, itemPathFn, false}
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister) actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater) actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
...@@ -484,7 +478,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag ...@@ -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. // 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) // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
if !hasSubresource { if !hasSubresource {
namer = scopeNaming{scope, a.group.Linker, itemPathFn, true}
actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister) actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister)
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList)
} }
...@@ -811,149 +804,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag ...@@ -811,149 +804,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
return &apiResource, nil 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 // This magic incantation returns *ptrToObject for an arbitrary pointer
func indirectArbitraryPointer(ptrToObject interface{}) interface{} { func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface() return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
......
...@@ -17,46 +17,9 @@ limitations under the License. ...@@ -17,46 +17,9 @@ limitations under the License.
package endpoints package endpoints
import ( import (
"bytes"
"testing" "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) { func TestIsVowel(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
......
...@@ -9950,7 +9950,6 @@ go_test( ...@@ -9950,7 +9950,6 @@ go_test(
"//vendor:k8s.io/apiserver/pkg/endpoints/request", "//vendor:k8s.io/apiserver/pkg/endpoints/request",
"//vendor:k8s.io/apiserver/pkg/endpoints/testing", "//vendor:k8s.io/apiserver/pkg/endpoints/testing",
"//vendor:k8s.io/apiserver/pkg/registry/rest", "//vendor:k8s.io/apiserver/pkg/registry/rest",
"//vendor:k8s.io/client-go/pkg/api",
], ],
) )
...@@ -10041,7 +10040,6 @@ go_test( ...@@ -10041,7 +10040,6 @@ go_test(
library = ":k8s.io/apiserver/pkg/endpoints/handlers", library = ":k8s.io/apiserver/pkg/endpoints/handlers",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//vendor:github.com/emicklei/go-restful",
"//vendor:github.com/evanphx/json-patch", "//vendor:github.com/evanphx/json-patch",
"//vendor:k8s.io/apimachinery/pkg/api/equality", "//vendor:k8s.io/apimachinery/pkg/api/equality",
"//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/api/errors",
...@@ -10064,6 +10062,7 @@ go_library( ...@@ -10064,6 +10062,7 @@ go_library(
srcs = [ srcs = [
"k8s.io/apiserver/pkg/endpoints/handlers/discovery.go", "k8s.io/apiserver/pkg/endpoints/handlers/discovery.go",
"k8s.io/apiserver/pkg/endpoints/handlers/doc.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/patch.go",
"k8s.io/apiserver/pkg/endpoints/handlers/proxy.go", "k8s.io/apiserver/pkg/endpoints/handlers/proxy.go",
"k8s.io/apiserver/pkg/endpoints/handlers/rest.go", "k8s.io/apiserver/pkg/endpoints/handlers/rest.go",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment