clarify api request information

pull/6/head
deads2k 2015-01-29 14:14:36 -05:00
parent 926f46bf8a
commit 1c9216a45e
9 changed files with 245 additions and 154 deletions

View File

@ -49,13 +49,14 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
// Initialize the custom handlers.
watchHandler := (&WatchHandler{
storage: a.restHandler.storage,
codec: a.restHandler.codec,
canonicalPrefix: a.restHandler.canonicalPrefix,
selfLinker: a.restHandler.selfLinker,
storage: a.restHandler.storage,
codec: a.restHandler.codec,
canonicalPrefix: a.restHandler.canonicalPrefix,
selfLinker: a.restHandler.selfLinker,
apiRequestInfoResolver: a.restHandler.apiRequestInfoResolver,
})
redirectHandler := (&RedirectHandler{a.restHandler.storage, a.restHandler.codec})
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.restHandler.storage, a.restHandler.codec})
redirectHandler := (&RedirectHandler{a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
for path, storage := range a.restHandler.storage {
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {

View File

@ -28,6 +28,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@ -57,7 +58,7 @@ type defaultAPIServer struct {
// Note: This method is used only in tests.
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
prefix := root + "/" + version
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl, mapper)
group := NewAPIGroupVersion(storage, codec, root, prefix, selfLinker, admissionControl, mapper)
container := restful.NewContainer()
container.Router(restful.CurlyRouter{})
mux := container.ServeMux
@ -85,15 +86,16 @@ type APIGroupVersion struct {
// This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server.
// TODO: add multitype codec serialization
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, apiRoot, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
return &APIGroupVersion{
handler: RESTHandler{
storage: storage,
codec: codec,
canonicalPrefix: canonicalPrefix,
selfLinker: selfLinker,
ops: NewOperations(),
admissionControl: admissionControl,
storage: storage,
codec: codec,
canonicalPrefix: canonicalPrefix,
selfLinker: selfLinker,
ops: NewOperations(),
admissionControl: admissionControl,
apiRequestInfoResolver: &APIRequestInfoResolver{util.NewStringSet(apiRoot), latest.RESTMapper},
},
mapper: mapper,
}

View File

@ -25,6 +25,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
@ -153,12 +154,13 @@ type RequestAttributeGetter interface {
}
type requestAttributeGetter struct {
userContexts authhandlers.RequestContext
userContexts authhandlers.RequestContext
apiRequestInfoResolver *APIRequestInfoResolver
}
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext) RequestAttributeGetter {
return &requestAttributeGetter{userContexts}
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
return &requestAttributeGetter{userContexts, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}}
}
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
@ -171,16 +173,16 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
attribs.ReadOnly = IsReadOnlyReq(*req)
namespace, kind, _, _ := KindAndNamespace(req)
apiRequestInfo, _ := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
// If a path follows the conventions of the REST object store, then
// we can extract the object Kind. Otherwise, not.
attribs.Kind = kind
attribs.Kind = apiRequestInfo.Resource
// If the request specifies a namespace, then the namespace is filled in.
// Assumes there is no empty string namespace. Unspecified results
// in empty (does not understand defaulting rules.)
attribs.Namespace = namespace
attribs.Namespace = apiRequestInfo.Namespace
return &attribs
}
@ -197,78 +199,136 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
})
}
// KindAndNamespace returns the kind, namespace, and path parts for the request relative to /{kind}/{name}
// APIRequestInfo holds information parsed from the http.Request
type APIRequestInfo struct {
// Verb is the kube verb associated with the request, not the http verb. This includes things like list and watch.
Verb string
APIVersion string
Namespace string
// Resource is the name of the resource being requested. This is not the kind. For example: pods
Resource string
// Kind is the type of object being manipulated. For example: Pod
Kind string
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
Name string
// Parts are the path parts for the request relative to /{resource}/{name}
Parts []string
}
type APIRequestInfoResolver struct {
apiPrefixes util.StringSet
restMapper meta.RESTMapper
}
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
// Valid Inputs:
// Storage paths
// /ns/{namespace}/{kind}
// /ns/{namespace}/{kind}/{resourceName}
// /{kind}
// /{kind}/{resourceName}
// /{kind}/{resourceName}?namespace={namespace}
// /{kind}?namespace={namespace}
// /ns/{namespace}/{resource}
// /ns/{namespace}/{resource}/{resourceName}
// /{resource}
// /{resource}/{resourceName}
// /{resource}/{resourceName}?namespace={namespace}
// /{resource}?namespace={namespace}
//
// Special verbs:
// /proxy/{kind}/{resourceName}
// /proxy/ns/{namespace}/{kind}/{resourceName}
// /redirect/ns/{namespace}/{kind}/{resourceName}
// /redirect/{kind}/{resourceName}
// /watch/{kind}
// /watch/ns/{namespace}/{kind}
// /proxy/{resource}/{resourceName}
// /proxy/ns/{namespace}/{resource}/{resourceName}
// /redirect/ns/{namespace}/{resource}/{resourceName}
// /redirect/{resource}/{resourceName}
// /watch/{resource}
// /watch/ns/{namespace}/{resource}
//
// Fully qualified paths for above:
// /api/{version}/*
// /api/{version}/*
func KindAndNamespace(req *http.Request) (namespace, kind string, parts []string, err error) {
parts = splitPath(req.URL.Path)
if len(parts) < 1 {
err = fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
return
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
requestInfo := APIRequestInfo{}
currentParts := splitPath(req.URL.Path)
if len(currentParts) < 1 {
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
}
// handle input of form /api/{version}/* by adjusting special paths
if parts[0] == "api" {
if len(parts) > 2 {
parts = parts[2:]
} else {
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
return
for _, currPrefix := range r.apiPrefixes.List() {
// handle input of form /api/{version}/* by adjusting special paths
if currentParts[0] == currPrefix {
if len(currentParts) > 1 {
requestInfo.APIVersion = currentParts[1]
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
} else {
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
}
}
}
// handle input of form /{specialVerb}/*
if _, ok := specialVerbs[parts[0]]; ok {
if len(parts) > 1 {
parts = parts[1:]
if _, ok := specialVerbs[currentParts[0]]; ok {
requestInfo.Verb = currentParts[0]
if len(currentParts) > 1 {
currentParts = currentParts[1:]
} else {
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
return
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
}
} else {
switch req.Method {
case "POST":
requestInfo.Verb = "create"
case "GET":
requestInfo.Verb = "get"
case "PUT":
requestInfo.Verb = "update"
case "DELETE":
requestInfo.Verb = "delete"
}
}
// URL forms: /ns/{namespace}/{resource}/*, where parts are adjusted to be relative to kind
if currentParts[0] == "ns" {
if len(currentParts) < 3 {
return requestInfo, fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
}
requestInfo.Resource = currentParts[2]
requestInfo.Namespace = currentParts[1]
currentParts = currentParts[2:]
} else {
// URL forms: /{resource}/*
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
// URL forms: /{resource}/{resourceName} use the "default" namespace if omitted from query param
// URL forms: /{resource} assume cross-namespace operation if omitted from query param
requestInfo.Resource = currentParts[0]
requestInfo.Namespace = req.URL.Query().Get("namespace")
if len(requestInfo.Namespace) == 0 {
if len(currentParts) > 1 || req.Method == "POST" {
requestInfo.Namespace = api.NamespaceDefault
} else {
requestInfo.Namespace = api.NamespaceAll
}
}
}
// URL forms: /ns/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
if parts[0] == "ns" {
if len(parts) < 3 {
err = fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
return
}
namespace = parts[1]
kind = parts[2]
parts = parts[2:]
return
// parsing successful, so we now know the proper value for .Parts
requestInfo.Parts = currentParts
// if there's another part remaining after the kind, then that's the resource name
if len(requestInfo.Parts) >= 2 {
requestInfo.Name = requestInfo.Parts[1]
}
// URL forms: /{kind}/*
// URL forms: POST /{kind} is a legacy API convention to create in "default" namespace
// URL forms: /{kind}/{resourceName} use the "default" namespace if omitted from query param
// URL forms: /{kind} assume cross-namespace operation if omitted from query param
kind = parts[0]
namespace = req.URL.Query().Get("namespace")
if len(namespace) == 0 {
if len(parts) > 1 || req.Method == "POST" {
namespace = api.NamespaceDefault
} else {
namespace = api.NamespaceAll
}
// if there's no name on the request and we thought it was a get before, then the actual verb is a list
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
requestInfo.Verb = "list"
}
return
// if we have a resource, we have a good shot at being able to determine kind
if len(requestInfo.Resource) > 0 {
_, requestInfo.Kind, _ = r.restMapper.VersionAndKindForResource(requestInfo.Resource)
}
return requestInfo, nil
}

View File

@ -23,6 +23,8 @@ import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
type fakeRL bool
@ -63,59 +65,78 @@ func TestReadOnly(t *testing.T) {
}
}
func TestKindAndNamespace(t *testing.T) {
func TestGetAPIRequestInfo(t *testing.T) {
successCases := []struct {
method string
url string
expectedNamespace string
expectedKind string
expectedParts []string
method string
url string
expectedVerb string
expectedAPIVersion string
expectedNamespace string
expectedResource string
expectedKind string
expectedName string
expectedParts []string
}{
// resource paths
{"GET", "/ns/other/pods", "other", "pods", []string{"pods"}},
{"GET", "/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
{"GET", "/pods", api.NamespaceAll, "pods", []string{"pods"}},
{"POST", "/pods", api.NamespaceDefault, "pods", []string{"pods"}},
{"GET", "/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
{"GET", "/pods?namespace=other", "other", "pods", []string{"pods"}},
{"GET", "/ns/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
{"GET", "/ns/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/pods/foo?namespace=other", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
// special verbs
{"GET", "/proxy/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
{"GET", "/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/redirect/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
{"GET", "/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
{"GET", "/watch/ns/other/pods", "other", "pods", []string{"pods"}},
{"GET", "/proxy/ns/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/redirect/ns/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
{"GET", "/watch/ns/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
// fully-qualified paths
{"GET", "/api/v1beta1/ns/other/pods", "other", "pods", []string{"pods"}},
{"GET", "/api/v1beta1/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods", api.NamespaceAll, "pods", []string{"pods"}},
{"POST", "/api/v1beta1/pods", api.NamespaceDefault, "pods", []string{"pods"}},
{"GET", "/api/v1beta1/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods?namespace=other", "other", "pods", []string{"pods"}},
{"GET", "/api/v1beta1/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
{"GET", "/api/v1beta1/watch/ns/other/pods", "other", "pods", []string{"pods"}},
{"GET", "/api/v1beta1/ns/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
{"GET", "/api/v1beta1/ns/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods/foo?namespace=other", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/pods?namespace=other", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
{"GET", "/api/v1beta1/watch/ns/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
}
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
for _, successCase := range successCases {
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
namespace, kind, parts, err := KindAndNamespace(req)
apiRequestInfo, err := apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
t.Errorf("Unexpected error for url: %s", successCase.url)
t.Errorf("Unexpected error for url: %s %v", successCase.url, err)
}
if successCase.expectedNamespace != namespace {
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, namespace)
if successCase.expectedVerb != apiRequestInfo.Verb {
t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb)
}
if successCase.expectedKind != kind {
t.Errorf("Unexpected resourceType for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, kind)
if successCase.expectedAPIVersion != apiRequestInfo.APIVersion {
t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion)
}
if !reflect.DeepEqual(successCase.expectedParts, parts) {
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, parts)
if successCase.expectedNamespace != apiRequestInfo.Namespace {
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
}
if successCase.expectedKind != apiRequestInfo.Kind {
t.Errorf("Unexpected kind for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, apiRequestInfo.Kind)
}
if successCase.expectedResource != apiRequestInfo.Resource {
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
}
if successCase.expectedName != apiRequestInfo.Name {
t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
}
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
}
}
@ -131,7 +152,7 @@ func TestKindAndNamespace(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error %v", err)
}
_, _, _, err = KindAndNamespace(req)
_, err = apiRequestInfoResolver.GetAPIRequestInfo(req)
if err == nil {
t.Errorf("Expected error for key: %s", k)
}

View File

@ -75,17 +75,20 @@ var tagsToAttrs = map[string]util.StringSet{
// ProxyHandler provides a http.Handler which will proxy traffic to locations
// specified by items implementing Redirector.
type ProxyHandler struct {
prefix string
storage map[string]RESTStorage
codec runtime.Codec
prefix string
storage map[string]RESTStorage
codec runtime.Codec
apiRequestInfoResolver *APIRequestInfoResolver
}
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
namespace, kind, parts, err := KindAndNamespace(req)
requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
return
}
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
ctx := api.WithNamespace(api.NewContext(), namespace)
if len(parts) < 2 {
notFound(w, req)
@ -103,17 +106,17 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
rest = rest + "/"
}
}
storage, ok := r.storage[kind]
storage, ok := r.storage[resource]
if !ok {
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
notFound(w, req)
return
}
redirector, ok := storage.(Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
errorJSON(errors.NewMethodNotSupported(kind, "proxy"), r.codec, w)
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
errorJSON(errors.NewMethodNotSupported(resource, "proxy"), r.codec, w)
return
}
@ -156,7 +159,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
proxy.Transport = &proxyTransport{
proxyScheme: req.URL.Scheme,
proxyHost: req.URL.Host,
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, kind, id),
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, resource, id),
}
proxy.FlushInterval = 200 * time.Millisecond
proxy.ServeHTTP(w, newReq)

View File

@ -26,35 +26,37 @@ import (
)
type RedirectHandler struct {
storage map[string]RESTStorage
codec runtime.Codec
storage map[string]RESTStorage
codec runtime.Codec
apiRequestInfoResolver *APIRequestInfoResolver
}
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
namespace, kind, parts, err := KindAndNamespace(req)
requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
return
}
ctx := api.WithNamespace(api.NewContext(), namespace)
resource, parts := requestInfo.Resource, requestInfo.Parts
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
// redirection requires /kind/resourceName path parts
// redirection requires /resource/resourceName path parts
if len(parts) != 2 || req.Method != "GET" {
notFound(w, req)
return
}
id := parts[1]
storage, ok := r.storage[kind]
storage, ok := r.storage[resource]
if !ok {
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
notFound(w, req)
return
}
redirector, ok := storage.(Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
errorJSON(errors.NewMethodNotSupported(kind, "redirect"), r.codec, w)
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w)
return
}

View File

@ -32,28 +32,29 @@ import (
// RESTHandler implements HTTP verbs on a set of RESTful resources identified by name.
type RESTHandler struct {
storage map[string]RESTStorage
codec runtime.Codec
canonicalPrefix string
selfLinker runtime.SelfLinker
ops *Operations
admissionControl admission.Interface
storage map[string]RESTStorage
codec runtime.Codec
canonicalPrefix string
selfLinker runtime.SelfLinker
ops *Operations
admissionControl admission.Interface
apiRequestInfoResolver *APIRequestInfoResolver
}
// ServeHTTP handles requests to all RESTStorage objects.
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
namespace, kind, parts, err := KindAndNamespace(req)
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
return
}
storage, ok := h.storage[kind]
storage, ok := h.storage[requestInfo.Resource]
if !ok {
notFound(w, req)
return
}
h.handleRESTStorage(parts, req, w, storage, namespace, kind)
h.handleRESTStorage(requestInfo.Parts, req, w, storage, requestInfo.Namespace, requestInfo.Resource)
}
// Sets the SelfLink field of the object.

View File

@ -36,10 +36,11 @@ import (
)
type WatchHandler struct {
storage map[string]RESTStorage
codec runtime.Codec
canonicalPrefix string
selfLinker runtime.SelfLinker
storage map[string]RESTStorage
codec runtime.Codec
canonicalPrefix string
selfLinker runtime.SelfLinker
apiRequestInfoResolver *APIRequestInfoResolver
}
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
@ -87,21 +88,21 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
namespace, kind, _, err := KindAndNamespace(req)
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
return
}
ctx := api.WithNamespace(api.NewContext(), namespace)
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
storage := h.storage[kind]
storage := h.storage[requestInfo.Resource]
if storage == nil {
notFound(w, req)
return
}
watcher, ok := storage.(ResourceWatcher)
if !ok {
errorJSON(errors.NewMethodNotSupported(kind, "watch"), h.codec, w)
errorJSON(errors.NewMethodNotSupported(requestInfo.Resource, "watch"), h.codec, w)
return
}

View File

@ -454,7 +454,7 @@ func (m *Master) init(c *Config) {
m.InsecureHandler = handler
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts)
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts, latest.RESTMapper, "api")
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
// Install Authenticator
@ -531,25 +531,25 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
}
// api_v1beta1 returns the resources and codec for API version v1beta1.
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage {
storage[k] = v
}
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
}
// api_v1beta2 returns the resources and codec for API version v1beta2.
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage {
storage[k] = v
}
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
}
// api_v1beta3 returns the resources and codec for API version v1beta3.
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage {
if k == "minions" {
@ -557,5 +557,5 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
}
storage[strings.ToLower(k)] = v
}
return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
}