mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-16 14:45:28 +00:00
Merge pull request #4395 from smarterclayton/split_naming
Fix cross-namespace LIST and WATCH
This commit is contained in:
@@ -42,6 +42,7 @@ type action struct {
|
||||
Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
|
||||
Path string // The path of the action
|
||||
Params []*restful.Parameter // List of parameters associated with the action.
|
||||
Namer ScopeNamer
|
||||
}
|
||||
|
||||
// errEmptyName is returned when API requests do not fill the name section of the path.
|
||||
@@ -87,7 +88,6 @@ func (a *APIInstaller) newWebService() *restful.WebService {
|
||||
func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler http.Handler, redirectHandler http.Handler, proxyHandler http.Handler) error {
|
||||
codec := a.group.codec
|
||||
admit := a.group.admit
|
||||
linker := a.group.linker
|
||||
context := a.group.context
|
||||
resource := path
|
||||
|
||||
@@ -149,13 +149,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
}
|
||||
|
||||
var ctxFn ContextFunc
|
||||
var namespaceFn ResourceNamespaceFunc
|
||||
var nameFn ResourceNameFunc
|
||||
var generateLinkFn linkFunc
|
||||
var objNameFn ObjectNameFunc
|
||||
linkFn := func(req *restful.Request, obj runtime.Object) error {
|
||||
return setSelfLink(obj, req.Request, a.group.linker, generateLinkFn)
|
||||
}
|
||||
ctxFn = func(req *restful.Request) api.Context {
|
||||
if ctx, ok := context.Get(req.Request); ok {
|
||||
return ctx
|
||||
@@ -168,146 +161,76 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
|
||||
params := []*restful.Parameter{}
|
||||
actions := []action{}
|
||||
|
||||
// Get the list of actions for the given scope.
|
||||
if scope.Name() != meta.RESTScopeNameNamespace {
|
||||
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
|
||||
name, err = linker.Name(obj)
|
||||
if len(name) == 0 {
|
||||
err = errEmptyName
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", path, params}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params}, allowWatchList)
|
||||
|
||||
itemPath := path + "/{name}"
|
||||
nameParams := append(params, nameParam)
|
||||
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||
return
|
||||
}
|
||||
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||
name = req.PathParameter("name")
|
||||
if len(name) == 0 {
|
||||
err = errEmptyName
|
||||
}
|
||||
return
|
||||
}
|
||||
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||
path = strings.Replace(itemPath, "{name}", name, 1)
|
||||
path = gpath.Join(a.prefix, path)
|
||||
return
|
||||
}
|
||||
namer := rootScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)}
|
||||
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
actions = appendIf(actions, action{"LIST", path, params, namer}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", path, params, namer}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList)
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
} else {
|
||||
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
|
||||
if name, err = linker.Name(obj); err != nil {
|
||||
return
|
||||
}
|
||||
namespace, err = linker.Namespace(obj)
|
||||
return
|
||||
}
|
||||
|
||||
// v1beta3 format with namespace in path
|
||||
if scope.ParamPath() {
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path
|
||||
namespaceParams := []*restful.Parameter{namespaceParam}
|
||||
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||
namespace = req.PathParameter(scope.ParamName())
|
||||
if len(namespace) == 0 {
|
||||
namespace = api.NamespaceDefault
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams}, allowWatchList)
|
||||
|
||||
itemPath := namespacedPath + "/{name}"
|
||||
nameParams := append(namespaceParams, nameParam)
|
||||
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||
namespace, _ = namespaceFn(req)
|
||||
name = req.PathParameter("name")
|
||||
if len(name) == 0 {
|
||||
err = errEmptyName
|
||||
}
|
||||
return
|
||||
}
|
||||
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||
path = strings.Replace(itemPath, "{name}", name, 1)
|
||||
path = strings.Replace(path, "{"+scope.ParamName()+"}", namespace, 1)
|
||||
path = gpath.Join(a.prefix, path)
|
||||
return
|
||||
}
|
||||
namer := scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), false}
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams, namer}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams, namer}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams, namer}, allowWatchList)
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
|
||||
// list across namespace.
|
||||
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params}, allowWatchList)
|
||||
namer = scopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath), true}
|
||||
actions = appendIf(actions, action{"LIST", path, params, namer}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, params, namer}, allowWatchList)
|
||||
|
||||
} else {
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
// v1beta1/v1beta2 format where namespace was a query parameter
|
||||
namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||
namespaceParams := []*restful.Parameter{namespaceParam}
|
||||
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||
namespace = req.QueryParameter(scope.ParamName())
|
||||
if len(namespace) == 0 {
|
||||
namespace = api.NamespaceDefault
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
actions = appendIf(actions, action{"LIST", path, namespaceParams}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", path, namespaceParams}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams}, allowWatchList)
|
||||
|
||||
itemPath := path + "/{name}"
|
||||
nameParams := append(namespaceParams, nameParam)
|
||||
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||
namespace, _ = namespaceFn(req)
|
||||
name = req.PathParameter("name")
|
||||
if len(name) == 0 {
|
||||
err = errEmptyName
|
||||
}
|
||||
return
|
||||
}
|
||||
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||
path = strings.Replace(itemPath, "{name}", name, -1)
|
||||
path = gpath.Join(a.prefix, path)
|
||||
if len(namespace) > 0 {
|
||||
values := make(url.Values)
|
||||
values.Set(scope.ParamName(), namespace)
|
||||
query = values.Encode()
|
||||
}
|
||||
return
|
||||
}
|
||||
namer := legacyScopeNaming{scope, a.group.linker, gpath.Join(a.prefix, itemPath)}
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"LIST", path, namespaceParams, namer}, storageVerbs["RESTLister"])
|
||||
actions = appendIf(actions, action{"POST", path, namespaceParams, namer}, storageVerbs["RESTCreater"])
|
||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams, namer}, allowWatchList)
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, storageVerbs["RESTGetter"])
|
||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, storageVerbs["RESTUpdater"])
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, storageVerbs["RESTDeleter"])
|
||||
actions = appendIf(actions, action{"WATCH", "/watch/" + itemPath, nameParams, namer}, storageVerbs["ResourceWatcher"])
|
||||
actions = appendIf(actions, action{"REDIRECT", "/redirect/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams, namer}, storageVerbs["Redirector"])
|
||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams, namer}, storageVerbs["Redirector"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +255,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
m := monitorFilter(action.Verb, resource)
|
||||
switch action.Verb {
|
||||
case "GET": // Get a resource.
|
||||
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, nameFn, linkFn, codec)).
|
||||
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, action.Namer, codec)).
|
||||
Filter(m).
|
||||
Doc("read the specified " + kind).
|
||||
Operation("read" + kind).
|
||||
@@ -340,7 +263,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "LIST": // List all resources of a kind.
|
||||
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, namespaceFn, linkFn, codec)).
|
||||
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, codec)).
|
||||
Filter(m).
|
||||
Doc("list objects of kind " + kind).
|
||||
Operation("list" + kind).
|
||||
@@ -348,7 +271,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "PUT": // Update a resource.
|
||||
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, nameFn, objNameFn, linkFn, codec, resource, admit)).
|
||||
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, action.Namer, codec, resource, admit)).
|
||||
Filter(m).
|
||||
Doc("update the specified " + kind).
|
||||
Operation("update" + kind).
|
||||
@@ -356,7 +279,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "POST": // Create a resource.
|
||||
route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, namespaceFn, linkFn, codec, resource, admit)).
|
||||
route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, action.Namer, codec, resource, admit)).
|
||||
Filter(m).
|
||||
Doc("create a " + kind).
|
||||
Operation("create" + kind).
|
||||
@@ -364,7 +287,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "DELETE": // Delete a resource.
|
||||
route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, nameFn, linkFn, codec, resource, kind, admit)).
|
||||
route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, action.Namer, codec, resource, kind, admit)).
|
||||
Filter(m).
|
||||
Doc("delete a " + kind).
|
||||
Operation("delete" + kind)
|
||||
@@ -409,6 +332,224 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
return 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
|
||||
itemPath string
|
||||
}
|
||||
|
||||
// rootScopeNaming implements ScopeNamer
|
||||
var _ 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) (path, query 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
|
||||
}
|
||||
}
|
||||
path = strings.Replace(n.itemPath, "{name}", name, 1)
|
||||
return path, "", nil
|
||||
}
|
||||
|
||||
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
|
||||
func (n rootScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
|
||||
path = req.Request.URL.Path
|
||||
return path, "", 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
|
||||
itemPath string
|
||||
allNamespaces bool
|
||||
}
|
||||
|
||||
// scopeNaming implements ScopeNamer
|
||||
var _ 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.ParamName())
|
||||
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) (path, query 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
|
||||
}
|
||||
}
|
||||
path = strings.Replace(n.itemPath, "{name}", name, 1)
|
||||
if !n.allNamespaces {
|
||||
path = strings.Replace(path, "{"+n.scope.ParamName()+"}", namespace, 1)
|
||||
}
|
||||
return path, "", nil
|
||||
}
|
||||
|
||||
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
|
||||
func (n scopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
|
||||
path = req.Request.URL.Path
|
||||
return path, "", 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
|
||||
}
|
||||
|
||||
// legacyScopeNaming modifies a scopeNaming to read namespace from the query. It implements
|
||||
// ScopeNamer for older query based namespace parameters.
|
||||
type legacyScopeNaming struct {
|
||||
scope meta.RESTScope
|
||||
runtime.SelfLinker
|
||||
itemPath string
|
||||
}
|
||||
|
||||
// legacyScopeNaming implements ScopeNamer
|
||||
var _ ScopeNamer = legacyScopeNaming{}
|
||||
|
||||
// Namespace returns the namespace from the query or the default.
|
||||
func (n legacyScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
|
||||
values, ok := req.Request.URL.Query()[n.scope.ParamName()]
|
||||
if !ok || len(values) == 0 {
|
||||
// legacy behavior
|
||||
if req.Request.Method == "POST" || len(req.PathParameter("name")) > 0 {
|
||||
return api.NamespaceDefault, nil
|
||||
}
|
||||
return api.NamespaceAll, nil
|
||||
}
|
||||
return values[0], nil
|
||||
}
|
||||
|
||||
// Name returns the name from the path, the namespace (or default), or an error if the
|
||||
// name is empty.
|
||||
func (n legacyScopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
|
||||
namespace, _ = n.Namespace(req)
|
||||
name = req.PathParameter("name")
|
||||
if len(name) == 0 {
|
||||
return "", "", errEmptyName
|
||||
}
|
||||
return namespace, name, nil
|
||||
}
|
||||
|
||||
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
|
||||
func (n legacyScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
|
||||
namespace, name, err := n.ObjectName(obj)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return "", "", errEmptyName
|
||||
}
|
||||
path = strings.Replace(n.itemPath, "{name}", name, -1)
|
||||
values := make(url.Values)
|
||||
values.Set(n.scope.ParamName(), namespace)
|
||||
query = values.Encode()
|
||||
return path, query, nil
|
||||
}
|
||||
|
||||
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
|
||||
func (n legacyScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
|
||||
namespace, err := n.Namespace(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
path = req.Request.URL.Path
|
||||
values := make(url.Values)
|
||||
values.Set(n.scope.ParamName(), namespace)
|
||||
query = values.Encode()
|
||||
return path, query, 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 legacyScopeNaming) 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()
|
||||
|
@@ -154,6 +154,9 @@ type SimpleRESTStorage struct {
|
||||
updated *Simple
|
||||
created *Simple
|
||||
|
||||
actualNamespace string
|
||||
namespacePresent bool
|
||||
|
||||
// These are set when Watch is called
|
||||
fakeWatch *watch.FakeWatcher
|
||||
requestedLabelSelector labels.Selector
|
||||
@@ -172,6 +175,7 @@ type SimpleRESTStorage struct {
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
result := &SimpleList{
|
||||
Items: storage.list,
|
||||
}
|
||||
@@ -179,10 +183,16 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Sele
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) checkContext(ctx api.Context) {
|
||||
storage.actualNamespace, storage.namespacePresent = api.NamespaceFrom(ctx)
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.deleted = id
|
||||
if err := storage.errors["delete"]; err != nil {
|
||||
return nil, err
|
||||
@@ -204,6 +214,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object {
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.created = obj.(*Simple)
|
||||
if err := storage.errors["create"]; err != nil {
|
||||
return nil, err
|
||||
@@ -216,6 +227,7 @@ func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (r
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.updated = obj.(*Simple)
|
||||
if err := storage.errors["update"]; err != nil {
|
||||
return nil, false, err
|
||||
@@ -229,6 +241,7 @@ func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (r
|
||||
|
||||
// Implement ResourceWatcher.
|
||||
func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.requestedLabelSelector = label
|
||||
storage.requestedFieldSelector = field
|
||||
storage.requestedResourceVersion = resourceVersion
|
||||
@@ -242,6 +255,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
|
||||
|
||||
// Implement Redirector.
|
||||
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
|
||||
storage.checkContext(ctx)
|
||||
// validate that the namespace context on the request matches the expected input
|
||||
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
|
||||
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
|
||||
@@ -388,29 +402,57 @@ func TestVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleList(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
storage["simple"] = &simpleStorage
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
namespace: "other",
|
||||
expectedSet: "/prefix/version/simple?namespace=other",
|
||||
func TestList(t *testing.T) {
|
||||
testCases := []struct {
|
||||
url string
|
||||
namespace string
|
||||
selfLink string
|
||||
legacy bool
|
||||
}{
|
||||
{"/prefix/version/simple", "", "/prefix/version/simple?namespace=", true},
|
||||
{"/prefix/version/simple?namespace=other", "other", "/prefix/version/simple?namespace=other", true},
|
||||
// list items across all namespaces
|
||||
{"/prefix/version/simple?namespace=", "", "/prefix/version/simple?namespace=", true},
|
||||
{"/prefix/version/namespaces/default/simple", "default", "/prefix/version/namespaces/default/simple", false},
|
||||
{"/prefix/version/namespaces/other/simple", "other", "/prefix/version/namespaces/other/simple", false},
|
||||
// list items across all namespaces
|
||||
{"/prefix/version/simple", "", "/prefix/version/simple", false},
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
for i, testCase := range testCases {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace}
|
||||
storage["simple"] = &simpleStorage
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
namespace: testCase.namespace,
|
||||
expectedSet: testCase.selfLink,
|
||||
}
|
||||
var handler http.Handler
|
||||
if testCase.legacy {
|
||||
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
} else {
|
||||
handler = Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
|
||||
}
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/prefix/version/simple")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
|
||||
}
|
||||
if !selfLinker.called {
|
||||
t.Errorf("Never set self link")
|
||||
resp, err := http.Get(server.URL + testCase.url)
|
||||
if err != nil {
|
||||
t.Errorf("%d: unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
|
||||
}
|
||||
// TODO: future, restore get links
|
||||
if !selfLinker.called {
|
||||
t.Errorf("%d: never set self link", i)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,7 +468,7 @@ func TestErrorList(t *testing.T) {
|
||||
|
||||
resp, err := http.Get(server.URL + "/prefix/version/simple")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
@@ -439,7 +481,54 @@ func TestNonEmptyList(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{
|
||||
list: []Simple{
|
||||
{
|
||||
TypeMeta: api.TypeMeta{Kind: "Simple"},
|
||||
ObjectMeta: api.ObjectMeta{Name: "something", Namespace: "other"},
|
||||
Other: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/prefix/version/simple")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
t.Logf("Data: %s", string(body))
|
||||
}
|
||||
|
||||
var listOut SimpleList
|
||||
body, err := extractBody(resp, &listOut)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(listOut.Items) != 1 {
|
||||
t.Errorf("Unexpected response: %#v", listOut)
|
||||
return
|
||||
}
|
||||
if listOut.Items[0].Other != simpleStorage.list[0].Other {
|
||||
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
|
||||
}
|
||||
if listOut.SelfLink != "/prefix/version/simple?namespace=" {
|
||||
t.Errorf("unexpected list self link: %#v", listOut)
|
||||
}
|
||||
expectedSelfLink := "/prefix/version/simple/something?namespace=other"
|
||||
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
|
||||
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfLinkSkipsEmptyName(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{
|
||||
list: []Simple{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Namespace: "other"},
|
||||
Other: "foo",
|
||||
},
|
||||
@@ -452,7 +541,7 @@ func TestNonEmptyList(t *testing.T) {
|
||||
|
||||
resp, err := http.Get(server.URL + "/prefix/version/simple")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
@@ -460,11 +549,10 @@ func TestNonEmptyList(t *testing.T) {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
t.Logf("Data: %s", string(body))
|
||||
}
|
||||
|
||||
var listOut SimpleList
|
||||
body, err := extractBody(resp, &listOut)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(listOut.Items) != 1 {
|
||||
@@ -474,7 +562,10 @@ func TestNonEmptyList(t *testing.T) {
|
||||
if listOut.Items[0].Other != simpleStorage.list[0].Other {
|
||||
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body))
|
||||
}
|
||||
expectedSelfLink := "/prefix/version/simple?namespace=other"
|
||||
if listOut.SelfLink != "/prefix/version/simple?namespace=" {
|
||||
t.Errorf("unexpected list self link: %#v", listOut)
|
||||
}
|
||||
expectedSelfLink := ""
|
||||
if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink {
|
||||
t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink)
|
||||
}
|
||||
@@ -817,6 +908,48 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// when the object name and namespace can't be retrieved, skip name checking
|
||||
func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
err: fmt.Errorf("test error"),
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
item := &Simple{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: ID,
|
||||
Namespace: "other", // does not match request
|
||||
},
|
||||
Other: "bar",
|
||||
}
|
||||
body, err := codec.Encode(item)
|
||||
if err != nil {
|
||||
// The following cases will fail, so die now
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
|
||||
_, err = client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name {
|
||||
t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
|
||||
}
|
||||
if selfLinker.called {
|
||||
t.Errorf("self link ignored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
@@ -931,20 +1064,79 @@ type setTestSelfLinker struct {
|
||||
name string
|
||||
namespace string
|
||||
called bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, nil }
|
||||
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, nil }
|
||||
func (*setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", nil }
|
||||
func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err }
|
||||
func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err }
|
||||
func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err }
|
||||
func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
|
||||
if e, a := s.expectedSet, selfLink; e != a {
|
||||
s.t.Errorf("expected '%v', got '%v'", e, a)
|
||||
}
|
||||
s.called = true
|
||||
return nil
|
||||
return s.err
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
storage := SimpleRESTStorage{
|
||||
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
return obj, nil
|
||||
},
|
||||
}
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
name: "bar",
|
||||
namespace: "default",
|
||||
expectedSet: "/prefix/version/foo/bar?namespace=default",
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
simple := &Simple{
|
||||
Other: "bar",
|
||||
}
|
||||
data, _ := codec.Encode(simple)
|
||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var response *http.Response
|
||||
go func() {
|
||||
response, err = client.Do(request)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var itemOut Simple
|
||||
body, err := extractBody(response, &itemOut)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&itemOut, simple) {
|
||||
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
|
||||
}
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
|
||||
}
|
||||
if !selfLinker.called {
|
||||
t.Errorf("Never set self link")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateInNamespace(t *testing.T) {
|
||||
storage := SimpleRESTStorage{
|
||||
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
@@ -18,6 +18,7 @@ package apiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
gpath "path"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
@@ -27,66 +28,70 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// ContextFunc returns a Context given a request - a context must be returned
|
||||
type ContextFunc func(req *restful.Request) api.Context
|
||||
|
||||
// ResourceNameFunc returns a name (and optional namespace) given a request - if no name is present
|
||||
// an error must be returned.
|
||||
type ResourceNameFunc func(req *restful.Request) (namespace, name string, err error)
|
||||
|
||||
// ObjectNameFunc returns the name (and optional namespace) of an object
|
||||
type ObjectNameFunc func(obj runtime.Object) (namespace, name string, err error)
|
||||
|
||||
// ResourceNamespaceFunc returns the namespace associated with the given request - if no namespace
|
||||
// is present an error must be returned.
|
||||
type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err error)
|
||||
|
||||
// LinkResourceFunc updates the provided object with a SelfLink that is appropriate for the current
|
||||
// request.
|
||||
type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error
|
||||
// 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 a path and query for a given runtime object that represents the canonical path.
|
||||
GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error)
|
||||
// GenerateLink creates a path and query for a list that represents the canonical path.
|
||||
GenerateListLink(req *restful.Request) (path, query string, err error)
|
||||
}
|
||||
|
||||
// GetResource returns a function that handles retrieving a single resource from a RESTStorage object.
|
||||
func GetResource(r RESTGetter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
func GetResource(r RESTGetter, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
namespace, name, err := nameFn(req)
|
||||
namespace, name, err := namer.Name(req)
|
||||
if err != nil {
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
item, err := r.Get(ctx, name)
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
|
||||
result, err := r.Get(ctx, name)
|
||||
if err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
if err := linkFn(req, item); err != nil {
|
||||
if err := setSelfLink(result, req, namer); err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
writeJSON(http.StatusOK, codec, item, w)
|
||||
writeJSON(http.StatusOK, codec, result, w)
|
||||
}
|
||||
}
|
||||
|
||||
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
|
||||
func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
func ListResource(r RESTLister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
namespace, err := namespaceFn(req)
|
||||
namespace, err := namer.Namespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
|
||||
label, err := labels.ParseSelector(req.Request.URL.Query().Get("labels"))
|
||||
if err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
@@ -98,36 +103,34 @@ func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespace
|
||||
return
|
||||
}
|
||||
|
||||
item, err := r.List(ctx, label, field)
|
||||
result, err := r.List(ctx, label, field)
|
||||
if err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
if err := linkFn(req, item); err != nil {
|
||||
if err := setListSelfLink(result, req, namer); err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
writeJSON(http.StatusOK, codec, item, w)
|
||||
writeJSON(http.StatusOK, codec, result, w)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateResource returns a function that will handle a resource creation.
|
||||
func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
func CreateResource(r RESTCreater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, err := namespaceFn(req)
|
||||
namespace, err := namer.Namespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
|
||||
body, err := readBody(req.Request)
|
||||
if err != nil {
|
||||
@@ -159,7 +162,7 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamesp
|
||||
return
|
||||
}
|
||||
|
||||
if err := linkFn(req, result); err != nil {
|
||||
if err := setSelfLink(result, req, namer); err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
@@ -169,22 +172,20 @@ func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamesp
|
||||
}
|
||||
|
||||
// UpdateResource returns a function that will handle a resource update
|
||||
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := nameFn(req)
|
||||
namespace, name, err := namer.Name(req)
|
||||
if err != nil {
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
|
||||
body, err := readBody(req.Request)
|
||||
if err != nil {
|
||||
@@ -198,20 +199,18 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
|
||||
return
|
||||
}
|
||||
|
||||
objNamespace, objName, err := objNameFunc(obj)
|
||||
if err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
if objName != name {
|
||||
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
|
||||
return
|
||||
}
|
||||
if len(namespace) > 0 {
|
||||
if len(objNamespace) > 0 && objNamespace != namespace {
|
||||
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
|
||||
// check the provided name against the request
|
||||
if objNamespace, objName, err := namer.ObjectName(obj); err == nil {
|
||||
if objName != name {
|
||||
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
|
||||
return
|
||||
}
|
||||
if len(namespace) > 0 {
|
||||
if len(objNamespace) > 0 && objNamespace != namespace {
|
||||
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
|
||||
@@ -231,7 +230,7 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
|
||||
return
|
||||
}
|
||||
|
||||
if err := linkFn(req, result); err != nil {
|
||||
if err := setSelfLink(result, req, namer); err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
@@ -245,14 +244,14 @@ func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, o
|
||||
}
|
||||
|
||||
// DeleteResource returns a function that will handle a resource deletion
|
||||
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
||||
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := nameFn(req)
|
||||
namespace, name, err := namer.Name(req)
|
||||
if err != nil {
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
@@ -290,7 +289,7 @@ func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, l
|
||||
} else {
|
||||
// when a non-status response is returned, set the self link
|
||||
if _, ok := result.(*api.Status); !ok {
|
||||
if err := linkFn(req, result); err != nil {
|
||||
if err := setSelfLink(result, req, namer); err != nil {
|
||||
errorJSON(err, codec, w)
|
||||
return
|
||||
}
|
||||
@@ -329,42 +328,58 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
||||
}
|
||||
}
|
||||
|
||||
type linkFunc func(namespace, name string) (path string, query string)
|
||||
|
||||
// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
|
||||
// plus the path and query generated by the provided linkFunc
|
||||
func setSelfLink(obj runtime.Object, req *http.Request, linker runtime.SelfLinker, fn linkFunc) error {
|
||||
namespace, err := linker.Namespace(obj)
|
||||
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
|
||||
// TODO: SelfLink generation should return a full URL?
|
||||
path, query, err := namer.GenerateLink(req, obj)
|
||||
if err == errEmptyName {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := linker.Name(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path, query := fn(namespace, name)
|
||||
|
||||
newURL := *req.URL
|
||||
newURL.Path = path
|
||||
newURL := *req.Request.URL
|
||||
// use only canonical paths
|
||||
newURL.Path = gpath.Clean(path)
|
||||
newURL.RawQuery = query
|
||||
newURL.Fragment = ""
|
||||
|
||||
if err := linker.SetSelfLink(obj, newURL.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
return namer.SetSelfLink(obj, newURL.String())
|
||||
}
|
||||
|
||||
// setListSelfLink sets the self link of a list to the base URL, then sets the self links
|
||||
// on all child objects returned.
|
||||
func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
|
||||
if !runtime.IsListType(obj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: List SelfLink generation should return a full URL?
|
||||
path, query, err := namer.GenerateListLink(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newURL := *req.Request.URL
|
||||
newURL.Path = path
|
||||
newURL.RawQuery = query
|
||||
// use the path that got us here
|
||||
newURL.Fragment = ""
|
||||
if err := namer.SetSelfLink(obj, newURL.String()); err != nil {
|
||||
glog.V(4).Infof("Unable to set self link on object: %v", err)
|
||||
}
|
||||
|
||||
// Set self-link of objects in the list.
|
||||
items, err := runtime.ExtractList(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range items {
|
||||
if err := setSelfLink(items[i], req, linker, fn); err != nil {
|
||||
if err := setSelfLink(items[i], req, namer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return runtime.SetList(obj, items)
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user