From 083ce268e02a4cf22cbb27fe172d42fef88c2d49 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 11 Feb 2015 17:09:25 -0500 Subject: [PATCH] Put user in context, map requests to context above resthandler layer --- pkg/api/requestcontext.go | 115 +++++++++++++++++++++++++++++ pkg/apiserver/api_installer.go | 22 ++++-- pkg/apiserver/apiserver.go | 8 +- pkg/apiserver/apiserver_test.go | 52 ++++++------- pkg/apiserver/handlers.go | 14 ++-- pkg/apiserver/proxy.go | 7 +- pkg/apiserver/proxy_test.go | 4 +- pkg/apiserver/redirect.go | 7 +- pkg/apiserver/redirect_test.go | 4 +- pkg/apiserver/resthandler.go | 23 +++--- pkg/apiserver/watch_test.go | 8 +- pkg/auth/handlers/handlers.go | 75 +++++-------------- pkg/auth/handlers/handlers_test.go | 52 ++++++++----- pkg/master/master.go | 45 ++++++++--- 14 files changed, 290 insertions(+), 146 deletions(-) create mode 100644 pkg/api/requestcontext.go diff --git a/pkg/api/requestcontext.go b/pkg/api/requestcontext.go new file mode 100644 index 00000000000..ac303df2add --- /dev/null +++ b/pkg/api/requestcontext.go @@ -0,0 +1,115 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "errors" + "net/http" + "sync" +) + +// RequestContextMapper keeps track of the context associated with a particular request +type RequestContextMapper interface { + // Get returns the context associated with the given request (if any), and true if the request has an associated context, and false if it does not. + Get(req *http.Request) (Context, bool) + // Update maps the request to the given context. If no context was previously associated with the request, an error is returned. + // Update should only be called with a descendant context of the previously associated context. + // Updating to an unrelated context may return an error in the future. + // The context associated with a request should only be updated by a limited set of callers. + // Valid examples include the authentication layer, or an audit/tracing layer. + Update(req *http.Request, context Context) error +} + +type requestContextMap struct { + contexts map[*http.Request]Context + lock sync.Mutex +} + +// NewRequestContextMapper returns a new RequestContextMapper. +// The returned mapper must be added as a request filter using NewRequestContextFilter. +func NewRequestContextMapper() RequestContextMapper { + return &requestContextMap{ + contexts: make(map[*http.Request]Context), + } +} + +// Get returns the context associated with the given request (if any), and true if the request has an associated context, and false if it does not. +// Get will only return a valid context when called from inside the filter chain set up by NewRequestContextFilter() +func (c *requestContextMap) Get(req *http.Request) (Context, bool) { + c.lock.Lock() + defer c.lock.Unlock() + context, ok := c.contexts[req] + return context, ok +} + +// Update maps the request to the given context. +// If no context was previously associated with the request, an error is returned and the context is ignored. +func (c *requestContextMap) Update(req *http.Request, context Context) error { + c.lock.Lock() + defer c.lock.Unlock() + if _, ok := c.contexts[req]; !ok { + return errors.New("No context associated") + } + // TODO: ensure the new context is a descendant of the existing one + c.contexts[req] = context + return nil +} + +// init maps the request to the given context and returns true if there was no context associated with the request already. +// if a context was already associated with the request, it ignores the given context and returns false. +// init is intentionally unexported to ensure that all init calls are paired with a remove after a request is handled +func (c *requestContextMap) init(req *http.Request, context Context) bool { + c.lock.Lock() + defer c.lock.Unlock() + if _, exists := c.contexts[req]; exists { + return false + } + c.contexts[req] = context + return true +} + +// remove is intentionally unexported to ensure that the context is not removed until a request is handled +func (c *requestContextMap) remove(req *http.Request) { + c.lock.Lock() + defer c.lock.Unlock() + delete(c.contexts, req) +} + +// NewRequestContextFilter ensures there is a Context object associated with the request before calling the passed handler. +// After the passed handler runs, the context is cleaned up. +func NewRequestContextFilter(mapper RequestContextMapper, handler http.Handler) (http.Handler, error) { + if mapper, ok := mapper.(*requestContextMap); ok { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if mapper.init(req, NewContext()) { + // If we were the ones to successfully initialize, pair with a remove + defer mapper.remove(req) + } + handler.ServeHTTP(w, req) + }), nil + } else { + return handler, errors.New("Unknown RequestContextMapper implementation.") + } + +} + +// IsEmpty returns true if there are no contexts registered, or an error if it could not be determined. Intended for use by tests. +func IsEmpty(requestsToContexts RequestContextMapper) (bool, error) { + if requestsToContexts, ok := requestsToContexts.(*requestContextMap); ok { + return len(requestsToContexts.contexts) == 0, nil + } + return true, errors.New("Unknown RequestContextMapper implementation") +} diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go index fc3c3b91574..03a771e136b 100644 --- a/pkg/apiserver/api_installer.go +++ b/pkg/apiserver/api_installer.go @@ -62,8 +62,8 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) { linker: a.group.linker, info: a.group.info, }) - redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.info}) - proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.info}) + redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.context, a.group.info}) + proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.context, a.group.info}) for path, storage := range a.group.storage { if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil { @@ -88,6 +88,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage codec := a.group.codec admit := a.group.admit linker := a.group.linker + context := a.group.context resource := path object := storage.New() @@ -147,6 +148,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage storageVerbs["Redirector"] = true } + var ctxFn ContextFunc var namespaceFn ResourceNamespaceFunc var nameFn ResourceNameFunc var generateLinkFn linkFunc @@ -154,6 +156,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage 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 + } + return api.NewContext() + } allowWatchList := storageVerbs["ResourceWatcher"] && storageVerbs["RESTLister"] // watching on lists is allowed only for kinds that support both watch and list. scope := mapping.Scope @@ -324,7 +332,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, nameFn, linkFn, codec)). + route := ws.GET(action.Path).To(GetResource(getter, ctxFn, nameFn, linkFn, codec)). Filter(m). Doc("read the specified " + kind). Operation("read" + kind). @@ -332,7 +340,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, namespaceFn, linkFn, codec)). + route := ws.GET(action.Path).To(ListResource(lister, ctxFn, namespaceFn, linkFn, codec)). Filter(m). Doc("list objects of kind " + kind). Operation("list" + kind). @@ -340,7 +348,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, nameFn, objNameFn, linkFn, codec, resource, admit)). + route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, nameFn, objNameFn, linkFn, codec, resource, admit)). Filter(m). Doc("update the specified " + kind). Operation("update" + kind). @@ -348,7 +356,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, namespaceFn, linkFn, codec, resource, admit)). + route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, namespaceFn, linkFn, codec, resource, admit)). Filter(m). Doc("create a " + kind). Operation("create" + kind). @@ -356,7 +364,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, nameFn, linkFn, codec, resource, kind, admit)). + route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, nameFn, linkFn, codec, resource, kind, admit)). Filter(m). Doc("delete a " + kind). Operation("delete" + kind) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 68db292b60a..5e58e199cc5 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -98,9 +98,9 @@ type defaultAPIServer struct { // as RESTful resources at prefix, serialized by codec, and also includes the support // http resources. // Note: This method is used only in tests. -func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler { +func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) http.Handler { prefix := path.Join(root, version) - group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, mapper) + group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, contextMapper, mapper) container := restful.NewContainer() container.Router(restful.CurlyRouter{}) mux := container.ServeMux @@ -121,6 +121,7 @@ type APIGroupVersion struct { prefix string linker runtime.SelfLinker admit admission.Interface + context api.RequestContextMapper mapper meta.RESTMapper // TODO: put me into a cleaner interface info *APIRequestInfoResolver @@ -131,13 +132,14 @@ 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, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion { +func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) *APIGroupVersion { return &APIGroupVersion{ storage: storage, codec: codec, prefix: prefix, linker: linker, admit: admissionControl, + context: contextMapper, mapper: mapper, info: &APIRequestInfoResolver{util.NewStringSet(root), latest.RESTMapper}, } diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 097ec005f4a..f4260c22514 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -57,6 +57,7 @@ var versioner runtime.ResourceVersioner = accessor var selfLinker runtime.SelfLinker = accessor var mapper, namespaceMapper, legacyNamespaceMapper meta.RESTMapper // The mappers with namespace and with legacy namespace scopes. var admissionControl admission.Interface +var requestContextMapper api.RequestContextMapper func interfacesFor(version string) (*meta.VersionInterfaces, error) { switch version { @@ -111,6 +112,7 @@ func init() { legacyNamespaceMapper = legacyNsMapper namespaceMapper = nsMapper admissionControl = admit.NewAlwaysAdmit() + requestContextMapper = api.NewRequestContextMapper() } type Simple struct { @@ -283,7 +285,7 @@ func TestNotFound(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &SimpleRESTStorage{}, - }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -335,7 +337,7 @@ func TestUnimplementedRESTStorage(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": UnimplementedRESTStorage{}, - }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -360,7 +362,7 @@ func TestUnimplementedRESTStorage(t *testing.T) { } func TestVersion(t *testing.T) { - handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -395,7 +397,7 @@ func TestSimpleList(t *testing.T) { namespace: "other", expectedSet: "/prefix/version/simple?namespace=other", } - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -418,7 +420,7 @@ func TestErrorList(t *testing.T) { errors: map[string]error{"list": fmt.Errorf("test Error")}, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -444,7 +446,7 @@ func TestNonEmptyList(t *testing.T) { }, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -492,7 +494,7 @@ func TestGet(t *testing.T) { namespace: "default", } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -531,7 +533,7 @@ func TestGetAlternateSelfLink(t *testing.T) { namespace: "test", } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, legacyNamespaceMapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper) server := httptest.NewServer(handler) defer server.Close() @@ -569,7 +571,7 @@ func TestGetNamespaceSelfLink(t *testing.T) { namespace: "foo", } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, namespaceMapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper) server := httptest.NewServer(handler) defer server.Close() @@ -598,7 +600,7 @@ func TestGetMissing(t *testing.T) { errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")}, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -617,7 +619,7 @@ func TestDelete(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -640,7 +642,7 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -662,7 +664,7 @@ func TestDeleteMissing(t *testing.T) { errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)}, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -689,7 +691,7 @@ func TestUpdate(t *testing.T) { name: ID, namespace: api.NamespaceDefault, } - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -726,7 +728,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -759,7 +761,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -788,7 +790,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -820,7 +822,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -855,7 +857,7 @@ func TestUpdateMissing(t *testing.T) { errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)}, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -889,7 +891,7 @@ func TestCreateNotFound(t *testing.T) { // See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092. errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")}, }, - }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -957,7 +959,7 @@ func TestCreate(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -1015,7 +1017,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) + }, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -1075,7 +1077,7 @@ func TestDelayReturnsError(t *testing.T) { return nil, apierrs.NewAlreadyExists("foo", "bar") }, } - handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -1141,7 +1143,7 @@ func TestCreateTimeout(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -1173,7 +1175,7 @@ func TestCORSAllowedOrigins(t *testing.T) { } handler := CORS( - Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper), + Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper), allowedOriginRegexps, nil, nil, "true", ) server := httptest.NewServer(handler) diff --git a/pkg/apiserver/handlers.go b/pkg/apiserver/handlers.go index dfc1711b353..583b9e58179 100644 --- a/pkg/apiserver/handlers.go +++ b/pkg/apiserver/handlers.go @@ -27,7 +27,6 @@ import ( "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" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" @@ -154,21 +153,24 @@ type RequestAttributeGetter interface { } type requestAttributeGetter struct { - userContexts authhandlers.RequestContext + requestContextMapper api.RequestContextMapper apiRequestInfoResolver *APIRequestInfoResolver } // NewAttributeGetter returns an object which implements the RequestAttributeGetter interface. -func NewRequestAttributeGetter(userContexts authhandlers.RequestContext, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter { - return &requestAttributeGetter{userContexts, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}} +func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter { + return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}} } func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes { attribs := authorizer.AttributesRecord{} - user, ok := r.userContexts.Get(req) + ctx, ok := r.requestContextMapper.Get(req) if ok { - attribs.User = user + user, ok := api.UserFrom(ctx) + if ok { + attribs.User = user + } } attribs.ReadOnly = IsReadOnlyReq(*req) diff --git a/pkg/apiserver/proxy.go b/pkg/apiserver/proxy.go index 1e8bc9c759c..32f39c6edc7 100644 --- a/pkg/apiserver/proxy.go +++ b/pkg/apiserver/proxy.go @@ -78,6 +78,7 @@ type ProxyHandler struct { prefix string storage map[string]RESTStorage codec runtime.Codec + context api.RequestContextMapper apiRequestInfoResolver *APIRequestInfoResolver } @@ -97,7 +98,11 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { verb = requestInfo.Verb namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts - ctx := api.WithNamespace(api.NewContext(), namespace) + ctx, ok := r.context.Get(req) + if !ok { + ctx = api.NewContext() + } + ctx = api.WithNamespace(ctx, namespace) if len(parts) < 2 { notFound(w, req) httpCode = http.StatusNotFound diff --git a/pkg/apiserver/proxy_test.go b/pkg/apiserver/proxy_test.go index f1c71addd91..cf412aa9500 100644 --- a/pkg/apiserver/proxy_test.go +++ b/pkg/apiserver/proxy_test.go @@ -281,12 +281,12 @@ func TestProxy(t *testing.T) { namespaceHandler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/prefix", "version", selfLinker, admissionControl, namespaceMapper) + }, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper) namespaceServer := httptest.NewServer(namespaceHandler) defer namespaceServer.Close() legacyNamespaceHandler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/prefix", "version", selfLinker, admissionControl, legacyNamespaceMapper) + }, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper) legacyNamespaceServer := httptest.NewServer(legacyNamespaceHandler) defer legacyNamespaceServer.Close() diff --git a/pkg/apiserver/redirect.go b/pkg/apiserver/redirect.go index f484f9e7548..f2451292d49 100644 --- a/pkg/apiserver/redirect.go +++ b/pkg/apiserver/redirect.go @@ -29,6 +29,7 @@ import ( type RedirectHandler struct { storage map[string]RESTStorage codec runtime.Codec + context api.RequestContextMapper apiRequestInfoResolver *APIRequestInfoResolver } @@ -47,7 +48,11 @@ func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } verb = requestInfo.Verb resource, parts := requestInfo.Resource, requestInfo.Parts - ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace) + ctx, ok := r.context.Get(req) + if !ok { + ctx = api.NewContext() + } + ctx = api.WithNamespace(ctx, requestInfo.Namespace) // redirection requires /resource/resourceName path parts if len(parts) != 2 || req.Method != "GET" { diff --git a/pkg/apiserver/redirect_test.go b/pkg/apiserver/redirect_test.go index 4c2946e7013..4098671f961 100644 --- a/pkg/apiserver/redirect_test.go +++ b/pkg/apiserver/redirect_test.go @@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/prefix", "version", selfLinker, admissionControl, mapper) + }, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -84,7 +84,7 @@ func TestRedirectWithNamespaces(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/prefix", "version", selfLinker, admissionControl, namespaceMapper) + }, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper) server := httptest.NewServer(handler) defer server.Close() diff --git a/pkg/apiserver/resthandler.go b/pkg/apiserver/resthandler.go index b09be207367..c2d27ac53c9 100644 --- a/pkg/apiserver/resthandler.go +++ b/pkg/apiserver/resthandler.go @@ -29,6 +29,9 @@ import ( "github.com/emicklei/go-restful" ) +// 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) @@ -45,7 +48,7 @@ type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err err type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error // GetResource returns a function that handles retrieving a single resource from a RESTStorage object. -func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction { +func GetResource(r RESTGetter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, name, err := nameFn(req) @@ -53,7 +56,7 @@ func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, notFound(w, req.Request) return } - ctx := api.NewContext() + ctx := ctxFn(req) if len(namespace) > 0 { ctx = api.WithNamespace(ctx, namespace) } @@ -71,7 +74,7 @@ func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, } // ListResource returns a function that handles retrieving a list of resources from a RESTStorage object. -func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction { +func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter @@ -80,7 +83,7 @@ func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkRe notFound(w, req.Request) return } - ctx := api.NewContext() + ctx := ctxFn(req) if len(namespace) > 0 { ctx = api.WithNamespace(ctx, namespace) } @@ -109,7 +112,7 @@ func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkRe } // CreateResource returns a function that will handle a resource creation. -func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { +func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter @@ -121,7 +124,7 @@ func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn Lin notFound(w, req.Request) return } - ctx := api.NewContext() + ctx := ctxFn(req) if len(namespace) > 0 { ctx = api.WithNamespace(ctx, namespace) } @@ -162,7 +165,7 @@ func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn Lin } // UpdateResource returns a function that will handle a resource update -func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { +func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter @@ -174,7 +177,7 @@ func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNa notFound(w, req.Request) return } - ctx := api.NewContext() + ctx := ctxFn(req) if len(namespace) > 0 { ctx = api.WithNamespace(ctx, namespace) } @@ -238,7 +241,7 @@ func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNa } // DeleteResource returns a function that will handle a resource deletion -func DeleteResource(r RESTDeleter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction { +func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter @@ -250,7 +253,7 @@ func DeleteResource(r RESTDeleter, nameFn ResourceNameFunc, linkFn LinkResourceF notFound(w, req.Request) return } - ctx := api.NewContext() + ctx := ctxFn(req) if len(namespace) > 0 { ctx = api.WithNamespace(ctx, namespace) } diff --git a/pkg/apiserver/watch_test.go b/pkg/apiserver/watch_test.go index ef33a2c32e0..d4a702addc8 100644 --- a/pkg/apiserver/watch_test.go +++ b/pkg/apiserver/watch_test.go @@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) { _ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work. handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/api", "version", selfLinker, admissionControl, mapper) + }, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) { simpleStorage := &SimpleRESTStorage{} handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/api", "version", selfLinker, admissionControl, mapper) + }, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) { simpleStorage := &SimpleRESTStorage{} handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/api", "version", selfLinker, admissionControl, mapper) + }, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) { simpleStorage := &SimpleRESTStorage{} handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/api", "version", selfLinker, admissionControl, mapper) + }, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper) server := httptest.NewServer(handler) defer server.Close() defer server.CloseClientConnections() diff --git a/pkg/auth/handlers/handlers.go b/pkg/auth/handlers/handlers.go index 68334381de6..d5f26cfc8f6 100644 --- a/pkg/auth/handlers/handlers.go +++ b/pkg/auth/handlers/handlers.go @@ -18,39 +18,35 @@ package handlers import ( "net/http" - "sync" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" - "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" "github.com/golang/glog" ) -// RequestContext is the interface used to associate a user with an http Request. -type RequestContext interface { - Set(*http.Request, user.Info) - Get(req *http.Request) (user.Info, bool) - Remove(*http.Request) -} - // NewRequestAuthenticator creates an http handler that tries to authenticate the given request as a user, and then // stores any such user found onto the provided context for the request. If authentication fails or returns an error // the failed handler is used. On success, handler is invoked to serve the request. -func NewRequestAuthenticator(context RequestContext, auth authenticator.Request, failed http.Handler, handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - user, ok, err := auth.AuthenticateRequest(req) - if err != nil || !ok { - if err != nil { - glog.Errorf("Unable to authenticate the request due to an error: %v", err) +func NewRequestAuthenticator(mapper api.RequestContextMapper, auth authenticator.Request, failed http.Handler, handler http.Handler) (http.Handler, error) { + return api.NewRequestContextFilter( + mapper, + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + user, ok, err := auth.AuthenticateRequest(req) + if err != nil || !ok { + if err != nil { + glog.Errorf("Unable to authenticate the request due to an error: %v", err) + } + failed.ServeHTTP(w, req) + return } - failed.ServeHTTP(w, req) - return - } - context.Set(req, user) - defer context.Remove(req) + if ctx, ok := mapper.Get(req); ok { + mapper.Update(req, api.WithUser(ctx, user)) + } - handler.ServeHTTP(w, req) - }) + handler.ServeHTTP(w, req) + }), + ) } var Unauthorized http.HandlerFunc = unauthorized @@ -59,38 +55,3 @@ var Unauthorized http.HandlerFunc = unauthorized func unauthorized(w http.ResponseWriter, req *http.Request) { http.Error(w, "Unauthorized", http.StatusUnauthorized) } - -// UserRequestContext allows different levels of a call stack to store/retrieve info about the -// current user associated with an http.Request. -type UserRequestContext struct { - requests map[*http.Request]user.Info - lock sync.Mutex -} - -// NewUserRequestContext provides a map for storing and retrieving users associated with requests. -// Be sure to pair each `context.Set(req, user)` call with a `defer context.Remove(req)` call or -// you will leak requests. It implements the RequestContext interface. -func NewUserRequestContext() *UserRequestContext { - return &UserRequestContext{ - requests: make(map[*http.Request]user.Info), - } -} - -func (c *UserRequestContext) Get(req *http.Request) (user.Info, bool) { - c.lock.Lock() - defer c.lock.Unlock() - user, ok := c.requests[req] - return user, ok -} - -func (c *UserRequestContext) Set(req *http.Request, user user.Info) { - c.lock.Lock() - defer c.lock.Unlock() - c.requests[req] = user -} - -func (c *UserRequestContext) Remove(req *http.Request) { - c.lock.Lock() - defer c.lock.Unlock() - delete(c.requests, req) -} diff --git a/pkg/auth/handlers/handlers_test.go b/pkg/auth/handlers/handlers_test.go index a7b2ef59ff3..103651d5237 100644 --- a/pkg/auth/handlers/handlers_test.go +++ b/pkg/auth/handlers/handlers_test.go @@ -22,15 +22,16 @@ import ( "net/http/httptest" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" ) func TestAuthenticateRequest(t *testing.T) { success := make(chan struct{}) - context := NewUserRequestContext() - auth := NewRequestAuthenticator( - context, + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { return &user.DefaultInfo{Name: "user"}, true, nil }), @@ -38,8 +39,13 @@ func TestAuthenticateRequest(t *testing.T) { t.Errorf("unexpected call to failed") }), http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { - if user, ok := context.Get(req); user == nil || !ok { - t.Errorf("no user stored on context: %#v", context) + ctx, ok := contextMapper.Get(req) + if ctx == nil || !ok { + t.Errorf("no context stored on contextMapper: %#v", contextMapper) + } + user, ok := api.UserFrom(ctx) + if user == nil || !ok { + t.Errorf("no user stored in context: %#v", ctx) } close(success) }), @@ -48,16 +54,20 @@ func TestAuthenticateRequest(t *testing.T) { auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) <-success - if len(context.requests) > 0 { - t.Errorf("context should have no stored requests: %v", context) + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) } } func TestAuthenticateRequestFailed(t *testing.T) { failed := make(chan struct{}) - context := NewUserRequestContext() - auth := NewRequestAuthenticator( - context, + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { return nil, false, nil }), @@ -72,16 +82,20 @@ func TestAuthenticateRequestFailed(t *testing.T) { auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) <-failed - if len(context.requests) > 0 { - t.Errorf("context should have no stored requests: %v", context) + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) } } func TestAuthenticateRequestError(t *testing.T) { failed := make(chan struct{}) - context := NewUserRequestContext() - auth := NewRequestAuthenticator( - context, + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { return nil, false, errors.New("failure") }), @@ -96,7 +110,11 @@ func TestAuthenticateRequestError(t *testing.T) { auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) <-failed - if len(context.requests) > 0 { - t.Errorf("context should have no stored requests: %v", context) + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) } } diff --git a/pkg/master/master.go b/pkg/master/master.go index 18bfd4d89fc..89970391395 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -89,6 +89,9 @@ type Config struct { AdmissionControl admission.Interface MasterServiceNamespace string + // Map requests to contexts. Exported so downstream consumers can provider their own mappers + RequestContextMapper api.RequestContextMapper + // If specified, all web services will be registered into this container RestfulContainer *restful.Container @@ -143,6 +146,7 @@ type Master struct { admissionControl admission.Interface masterCount int v1beta3 bool + requestContextMapper api.RequestContextMapper publicIP net.IP publicReadOnlyPort int @@ -225,6 +229,9 @@ func setDefaults(c *Config) { time.Sleep(5 * time.Second) } } + if c.RequestContextMapper == nil { + c.RequestContextMapper = api.NewRequestContextMapper() + } } // New returns a new instance of Master from the given config. @@ -292,6 +299,7 @@ func New(c *Config) *Master { authorizer: c.Authorizer, admissionControl: c.AdmissionControl, v1beta3: c.EnableV1Beta3, + requestContextMapper: c.RequestContextMapper, cacheTimeout: c.CacheTimeout, @@ -367,8 +375,6 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) // init initializes master. func (m *Master) init(c *Config) { - var userContexts = handlers.NewUserRequestContext() - var authenticator = c.Authenticator nodeRESTStorage := minion.NewREST(m.minionRegistry) podCache := NewPodCache( @@ -453,12 +459,16 @@ func (m *Master) init(c *Config) { m.InsecureHandler = handler - attributeGetter := apiserver.NewRequestAttributeGetter(userContexts, latest.RESTMapper, "api") + attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.RESTMapper, "api") handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer) // Install Authenticator - if authenticator != nil { - handler = handlers.NewRequestAuthenticator(userContexts, authenticator, handlers.Unauthorized, handler) + if c.Authenticator != nil { + authenticatedHandler, err := handlers.NewRequestAuthenticator(m.requestContextMapper, c.Authenticator, handlers.Unauthorized, handler) + if err != nil { + glog.Fatalf("Could not initialize authenticator: %v", err) + } + handler = authenticatedHandler } // Install root web services @@ -471,6 +481,19 @@ func (m *Master) init(c *Config) { m.InstallSwaggerAPI() } + // After all wrapping is done, put a context filter around both handlers + if handler, err := api.NewRequestContextFilter(m.requestContextMapper, m.Handler); err != nil { + glog.Fatalf("Could not initialize request context filter: %v", err) + } else { + m.Handler = handler + } + + if handler, err := api.NewRequestContextFilter(m.requestContextMapper, m.InsecureHandler); err != nil { + glog.Fatalf("Could not initialize request context filter: %v", err) + } else { + m.InsecureHandler = handler + } + // TODO: Attempt clean shutdown? m.masterServices.Start() } @@ -530,25 +553,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, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) { +func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { storage := make(map[string]apiserver.RESTStorage) for k, v := range m.storage { storage[k] = v } - return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper + return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, m.requestContextMapper, 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, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) { +func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { storage := make(map[string]apiserver.RESTStorage) for k, v := range m.storage { storage[k] = v } - return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper + return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, m.requestContextMapper, 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, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) { +func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) { storage := make(map[string]apiserver.RESTStorage) for k, v := range m.storage { if k == "minions" { @@ -556,5 +579,5 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, } storage[strings.ToLower(k)] = v } - return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper + return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper }