diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go index 4c9bb1ac234..ce21e230855 100644 --- a/pkg/apiserver/api_installer.go +++ b/pkg/apiserver/api_installer.go @@ -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 { diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 7d1f572a8c5..62002dbcdb9 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -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, } diff --git a/pkg/apiserver/handlers.go b/pkg/apiserver/handlers.go index 46eda63af0c..65ac7917b4b 100644 --- a/pkg/apiserver/handlers.go +++ b/pkg/apiserver/handlers.go @@ -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 } diff --git a/pkg/apiserver/handlers_test.go b/pkg/apiserver/handlers_test.go index e4ad0fec932..3cc5b05d685 100644 --- a/pkg/apiserver/handlers_test.go +++ b/pkg/apiserver/handlers_test.go @@ -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) } diff --git a/pkg/apiserver/proxy.go b/pkg/apiserver/proxy.go index b0e8151f032..6a3609ea8bf 100644 --- a/pkg/apiserver/proxy.go +++ b/pkg/apiserver/proxy.go @@ -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) diff --git a/pkg/apiserver/redirect.go b/pkg/apiserver/redirect.go index bd6f5b5c47d..1c817d5bebe 100644 --- a/pkg/apiserver/redirect.go +++ b/pkg/apiserver/redirect.go @@ -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 } diff --git a/pkg/apiserver/resthandler.go b/pkg/apiserver/resthandler.go index cb34aa1e233..22678f4c900 100644 --- a/pkg/apiserver/resthandler.go +++ b/pkg/apiserver/resthandler.go @@ -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. diff --git a/pkg/apiserver/watch.go b/pkg/apiserver/watch.go index 867259a7366..4b2b4e17ef9 100644 --- a/pkg/apiserver/watch.go +++ b/pkg/apiserver/watch.go @@ -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 } diff --git a/pkg/master/master.go b/pkg/master/master.go index fabc8dee29c..7d0a90d9270 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -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 }