diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 2a13f1a3561..519c270e340 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -103,7 +103,43 @@ func init() { return interfaces, true }, ) - mapper.Add(api.Scheme, true, "v1beta1", "v1beta2") - mapper.Add(api.Scheme, false, "v1beta3") + // list of versions we support on the server + versions := []string{"v1beta1", "v1beta2", "v1beta3"} + + // versions that used mixed case URL formats + versionMixedCase := map[string]bool{ + "v1beta1": true, + "v1beta2": true, + } + + // backwards compatibility, prior to v1beta3, we identified the namespace as a query parameter + versionToNamespaceScope := map[string]meta.RESTScope{ + "v1beta1": meta.RESTScopeNamespaceLegacy, + "v1beta2": meta.RESTScopeNamespaceLegacy, + "v1beta3": meta.RESTScopeNamespace, + } + + // the list of kinds that are scoped at the root of the api hierarchy + // if a kind is not enumerated here, it is assumed to have a namespace scope + kindToRootScope := map[string]bool{ + "Node": true, + "Minion": true, + } + + // enumerate all supported versions, get the kinds, and register with the mapper how to address our resources + for _, version := range versions { + for kind := range api.Scheme.KnownTypes(version) { + mixedCase, found := versionMixedCase[version] + if !found { + mixedCase = false + } + scope := versionToNamespaceScope[version] + _, found = kindToRootScope[kind] + if found { + scope = meta.RESTScopeRoot + } + mapper.Add(scope, kind, version, mixedCase) + } + } RESTMapper = mapper } diff --git a/pkg/api/meta/interfaces.go b/pkg/api/meta/interfaces.go index 17161d1e471..64d17ffd7d7 100644 --- a/pkg/api/meta/interfaces.go +++ b/pkg/api/meta/interfaces.go @@ -94,6 +94,30 @@ type MetadataAccessor interface { runtime.ResourceVersioner } +type RESTScopeName string + +const ( + RESTScopeNameNamespace RESTScopeName = "namespace" + RESTScopeNameRoot RESTScopeName = "root" +) + +// RESTScope contains the information needed to deal with REST resources that are in a resource hierarchy +// TODO After we deprecate v1beta1 and v1beta2, we can look a supporting removing the flexibility of supporting +// either a query or path param, and instead just support path param +type RESTScope interface { + // Name of the scope + Name() RESTScopeName + // ParamName is the optional name of the parameter that should be inserted in the resource url + // If empty, no param will be inserted + ParamName() string + // ParamPath is a boolean that controls how the parameter is manifested in resource paths + // If true, this parameter is encoded in path (i.e. /{paramName}/{paramValue}) + // If false, this parameter is encoded in query (i.e. ?{paramName}={paramValue}) + ParamPath() bool + // ParamDescription is the optional description to use to document the parameter in api documentation + ParamDescription() string +} + // RESTMapping contains the information needed to deal with objects of a specific // resource and kind in a RESTful manner. type RESTMapping struct { @@ -104,6 +128,9 @@ type RESTMapping struct { APIVersion string Kind string + // Scope contains the information needed to deal with REST Resources that are in a resource hierarchy + Scope RESTScope + runtime.Codec runtime.ObjectConvertor MetadataAccessor diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 9ce592e4396..bf9fd70f832 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -19,10 +19,47 @@ package meta import ( "fmt" "strings" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) +// Implements RESTScope interface +type restScope struct { + name RESTScopeName + paramName string + paramPath bool + paramDescription string +} + +func (r *restScope) Name() RESTScopeName { + return r.name +} +func (r *restScope) ParamName() string { + return r.paramName +} +func (r *restScope) ParamPath() bool { + return r.paramPath +} +func (r *restScope) ParamDescription() string { + return r.paramDescription +} + +var RESTScopeNamespaceLegacy = &restScope{ + name: RESTScopeNameNamespace, + paramName: "namespace", + paramPath: false, + paramDescription: "object name and auth scope, such as for teams and projects", +} + +var RESTScopeNamespace = &restScope{ + name: RESTScopeNameNamespace, + paramName: "ns", + paramPath: true, + paramDescription: "object name and auth scope, such as for teams and projects", +} + +var RESTScopeRoot = &restScope{ + name: RESTScopeNameRoot, +} + // typeMeta is used as a key for lookup in the mapping between REST path and // API object. type typeMeta struct { @@ -45,6 +82,7 @@ type typeMeta struct { type DefaultRESTMapper struct { mapping map[string]typeMeta reverse map[typeMeta]string + scopes map[typeMeta]RESTScope versions []string interfacesFunc VersionInterfacesFunc } @@ -61,40 +99,38 @@ type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool) func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper { mapping := make(map[string]typeMeta) reverse := make(map[typeMeta]string) + scopes := make(map[typeMeta]RESTScope) // TODO: verify name mappings work correctly when versions differ return &DefaultRESTMapper{ - mapping: mapping, - reverse: reverse, - + mapping: mapping, + reverse: reverse, + scopes: scopes, versions: versions, interfacesFunc: f, } } -// Add adds objects from a runtime.Scheme and its named versions to this map. -// If mixedCase is true, the legacy v1beta1/v1beta2 Kubernetes resource naming convention -// will be applied (camelCase vs lowercase). -func (m *DefaultRESTMapper) Add(scheme *runtime.Scheme, mixedCase bool, versions ...string) { - for _, version := range versions { - for kind := range scheme.KnownTypes(version) { - plural, singular := kindToResource(kind, mixedCase) - meta := typeMeta{APIVersion: version, Kind: kind} - if _, ok := m.mapping[plural]; !ok { - m.mapping[plural] = meta - m.mapping[singular] = meta - if strings.ToLower(plural) != plural { - m.mapping[strings.ToLower(plural)] = meta - m.mapping[strings.ToLower(singular)] = meta - } - } - m.reverse[meta] = plural +func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) { + plural, singular := kindToResource(kind, mixedCase) + meta := typeMeta{APIVersion: version, Kind: kind} + if _, ok := m.mapping[plural]; !ok { + m.mapping[plural] = meta + m.mapping[singular] = meta + if strings.ToLower(plural) != plural { + m.mapping[strings.ToLower(plural)] = meta + m.mapping[strings.ToLower(singular)] = meta } } + m.reverse[meta] = plural + m.scopes[meta] = scope } // kindToResource converts Kind to a resource name. func kindToResource(kind string, mixedCase bool) (plural, singular string) { + if len(kind) == 0 { + return + } if mixedCase { // Legacy support for mixed case names singular = strings.ToLower(kind[:1]) + kind[1:] @@ -167,6 +203,12 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", version, kind) } + // Ensure we have a REST scope + scope, ok := m.scopes[typeMeta{APIVersion: version, Kind: kind}] + if !ok { + return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", version, kind) + } + interfaces, ok := m.interfacesFunc(version) if !ok { return nil, fmt.Errorf("the provided version %q has no relevant versions", version) @@ -176,6 +218,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM Resource: resource, APIVersion: version, Kind: kind, + Scope: scope, Codec: interfaces.Codec, ObjectConvertor: interfaces.ObjectConvertor, diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 62b25111182..d849fdd1535 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -75,10 +75,7 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) { } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces) - scheme := runtime.NewScheme() - scheme.AddKnownTypes("test", &InternalObject{}) - mapper.Add(scheme, testCase.MixedCase, "test") - + mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) v, k, err := mapper.VersionAndKindForResource(testCase.Resource) hasErr := err != nil if hasErr != testCase.Err { @@ -150,10 +147,7 @@ func TestRESTMapperRESTMapping(t *testing.T) { } for i, testCase := range testCases { mapper := NewDefaultRESTMapper(testCase.DefaultVersions, fakeInterfaces) - scheme := runtime.NewScheme() - scheme.AddKnownTypes("test", &InternalObject{}) - mapper.Add(scheme, testCase.MixedCase, "test") - + mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase) mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...) hasErr := err != nil if hasErr != testCase.Err { @@ -180,11 +174,8 @@ func TestRESTMapperRESTMapping(t *testing.T) { func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, fakeInterfaces) - scheme := runtime.NewScheme() - scheme.AddKnownTypes("test1", &InternalObject{}) - scheme.AddKnownTypeWithName("test2", "OtherObject", &InternalObject{}) - scheme.AddKnownTypeWithName("test3", "OtherObject", &InternalObject{}) - mapper.Add(scheme, false, "test1", "test2") + mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) + mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false) // pick default matching object kind based on search order mapping, err := mapper.RESTMapping("OtherObject") @@ -236,10 +227,7 @@ func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, unmatchedVersionInterfaces) - scheme := runtime.NewScheme() - scheme.AddKnownTypes("test1", &InternalObject{}) - mapper.Add(scheme, false, "test1") - + mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) _, err := mapper.RESTMapping("InternalObject", "test1") if err == nil { t.Errorf("unexpected non-error") diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 54aad06a9e2..ec408bbf222 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -29,6 +29,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -55,9 +56,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, selfLinker runtime.SelfLinker, admissionControl admission.Interface) http.Handler { +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) + group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl, mapper) container := restful.NewContainer() mux := container.ServeMux group.InstallREST(container, mux, root, version) @@ -76,6 +77,7 @@ func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, ve // TODO: consider migrating this to go-restful which is a more full-featured version of the same thing. type APIGroupVersion struct { handler RESTHandler + mapper meta.RESTMapper } // NewAPIGroupVersion returns an object that will serve a set of REST resources and their @@ -83,15 +85,18 @@ 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) *APIGroupVersion { - return &APIGroupVersion{RESTHandler{ - storage: storage, - codec: codec, - canonicalPrefix: canonicalPrefix, - selfLinker: selfLinker, - ops: NewOperations(), - admissionControl: admissionControl, - }} +func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, 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, + }, + mapper: mapper, + } } // This magic incantation returns *ptrToObject for an arbitrary pointer @@ -99,7 +104,7 @@ func indirectArbitraryPointer(ptrToObject interface{}) interface{} { return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface() } -func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, h restful.RouteFunction, namespaceScope bool) error { +func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, h restful.RouteFunction, mapper meta.RESTMapper) error { object := storage.New() _, kind, err := api.Scheme.ObjectVersionAndKind(object) if err != nil { @@ -111,31 +116,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin } versionedObject := indirectArbitraryPointer(versionedPtr) - // See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic - // and status-code behavior - if namespaceScope { - path = "ns/{namespace}/" + path - } - - glog.V(5).Infof("Installing version=/%s, kind=/%s, path=/%s", version, kind, path) - - nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string") - namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string") - - createRoute := ws.POST(path).To(h). - Doc("create a " + kind). - Operation("create" + kind) - addParamIf(createRoute, namespaceParam, namespaceScope) - if _, ok := storage.(RESTCreater); ok { - ws.Route(createRoute.Reads(versionedObject)) // from the request - } else { - ws.Route(createRoute.Returns(http.StatusMethodNotAllowed, "creating objects is not supported", nil)) - } - - listRoute := ws.GET(path).To(h). - Doc("list objects of kind " + kind). - Operation("list" + kind) - addParamIf(listRoute, namespaceParam, namespaceScope) + var versionedList interface{} if lister, ok := storage.(RESTLister); ok { list := lister.NewList() _, listKind, err := api.Scheme.ObjectVersionAndKind(list) @@ -143,56 +124,171 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin if err != nil { return err } - versionedList := indirectArbitraryPointer(versionedListPtr) - glog.V(5).Infoln("type: ", reflect.TypeOf(versionedList)) - ws.Route(listRoute.Returns(http.StatusOK, "OK", versionedList)) - } else { - ws.Route(listRoute.Returns(http.StatusMethodNotAllowed, "listing objects is not supported", nil)) + versionedList = indirectArbitraryPointer(versionedListPtr) } - getRoute := ws.GET(path + "/{name}").To(h). - Doc("read the specified " + kind). - Operation("read" + kind). - Param(nameParam) - addParamIf(getRoute, namespaceParam, namespaceScope) + mapping, err := mapper.RESTMapping(kind, version) + if err != nil { + glog.V(1).Infof("OH NOES kind %s version %s err: %v", kind, version, err) + return err + } + + // names are always in path + nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string") + + // what verbs are supported by the storage, used to know what verbs we support per path + storageVerbs := map[string]bool{} + if _, ok := storage.(RESTCreater); ok { + storageVerbs["RESTCreater"] = true + } + if _, ok := storage.(RESTLister); ok { + storageVerbs["RESTLister"] = true + } if _, ok := storage.(RESTGetter); ok { - ws.Route(getRoute.Writes(versionedObject)) // on the response - } else { - ws.Route(getRoute.Returns(http.StatusMethodNotAllowed, "reading individual objects is not supported", nil)) + storageVerbs["RESTGetter"] = true } - - updateRoute := ws.PUT(path + "/{name}").To(h). - Doc("update the specified " + kind). - Operation("update" + kind). - Param(nameParam) - addParamIf(updateRoute, namespaceParam, namespaceScope) - if _, ok := storage.(RESTUpdater); ok { - ws.Route(updateRoute.Reads(versionedObject)) // from the request - } else { - ws.Route(updateRoute.Returns(http.StatusMethodNotAllowed, "updating objects is not supported", nil)) - } - - // TODO: Support PATCH - deleteRoute := ws.DELETE(path + "/{name}").To(h). - Doc("delete the specified " + kind). - Operation("delete" + kind). - Param(nameParam) - addParamIf(deleteRoute, namespaceParam, namespaceScope) if _, ok := storage.(RESTDeleter); ok { - ws.Route(deleteRoute) - } else { - ws.Route(deleteRoute.Returns(http.StatusMethodNotAllowed, "deleting objects is not supported", nil)) + storageVerbs["RESTDeleter"] = true + } + if _, ok := storage.(RESTUpdater); ok { + storageVerbs["RESTUpdater"] = true } + // path to verbs takes a path, and set of http verbs we should claim to support on this path + // given current url formats, this is scope specific + // for example, in v1beta3 url styles we would support the following: + // /ns/{namespace}/{resource} = []{POST, GET} // list resources in this namespace scope + // /ns/{namespace}/{resource}/{name} = []{GET, PUT, DELETE} // handle an individual resource + // /{resource} = []{GET} // list resources across all namespaces + pathToVerbs := map[string][]string{} + // similar to the above, it maps the ordered set of parameters that need to be documented + pathToParam := map[string][]*restful.Parameter{} + // pathToStorageVerb lets us distinguish between RESTLister and RESTGetter on verb=GET + pathToStorageVerb := map[string]string{} + scope := mapping.Scope + if scope.Name() != meta.RESTScopeNameNamespace { + // non-namespaced resources support a smaller set of resource paths + resourcesPath := path + resourcesPathVerbs := []string{} + resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "POST", storageVerbs["RESTCreater"]) + resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "GET", storageVerbs["RESTLister"]) + pathToVerbs[resourcesPath] = resourcesPathVerbs + pathToParam[resourcesPath] = []*restful.Parameter{} + pathToStorageVerb[resourcesPath] = "RESTLister" + + itemPath := resourcesPath + "/{name}" + itemPathVerbs := []string{} + itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"]) + pathToVerbs[itemPath] = itemPathVerbs + pathToParam[itemPath] = []*restful.Parameter{nameParam} + pathToStorageVerb[itemPath] = "RESTGetter" + + } else { + // v1beta3 format with namespace in path + if scope.ParamPath() { + namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") + + resourceByNamespace := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path + resourceByNamespaceVerbs := []string{} + resourceByNamespaceVerbs = appendStringIf(resourceByNamespaceVerbs, "POST", storageVerbs["RESTCreater"]) + resourceByNamespaceVerbs = appendStringIf(resourceByNamespaceVerbs, "GET", storageVerbs["RESTLister"]) + pathToVerbs[resourceByNamespace] = resourceByNamespaceVerbs + pathToParam[resourceByNamespace] = []*restful.Parameter{namespaceParam} + pathToStorageVerb[resourceByNamespace] = "RESTLister" + + itemPath := resourceByNamespace + "/{name}" + itemPathVerbs := []string{} + itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"]) + pathToVerbs[itemPath] = itemPathVerbs + pathToParam[itemPath] = []*restful.Parameter{namespaceParam, nameParam} + pathToStorageVerb[itemPath] = "RESTGetter" + + listAcrossNamespace := path + listAcrossNamespaceVerbs := []string{} + listAcrossNamespaceVerbs = appendStringIf(listAcrossNamespaceVerbs, "GET", storageVerbs["RESTLister"]) + pathToVerbs[listAcrossNamespace] = listAcrossNamespaceVerbs + pathToParam[listAcrossNamespace] = []*restful.Parameter{} + pathToStorageVerb[listAcrossNamespace] = "RESTLister" + + } else { + // v1beta1/v1beta2 format where namespace was a query parameter + namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string") + + resourcesPath := path + resourcesPathVerbs := []string{} + resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "POST", storageVerbs["RESTCreater"]) + resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "GET", storageVerbs["RESTLister"]) + pathToVerbs[resourcesPath] = resourcesPathVerbs + pathToParam[resourcesPath] = []*restful.Parameter{namespaceParam} + pathToStorageVerb[resourcesPath] = "RESTLister" + + itemPath := resourcesPath + "/{name}" + itemPathVerbs := []string{} + itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"]) + itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"]) + pathToVerbs[itemPath] = itemPathVerbs + pathToParam[itemPath] = []*restful.Parameter{namespaceParam, nameParam} + pathToStorageVerb[itemPath] = "RESTGetter" + } + } + + // See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic + // and status-code behavior + for path, verbs := range pathToVerbs { + glog.V(5).Infof("Installing version=/%s, kind=/%s, path=/%s", version, kind, path) + + params := pathToParam[path] + for _, verb := range verbs { + var route *restful.RouteBuilder + switch verb { + case "POST": + route = ws.POST(path).To(h). + Doc("create a " + kind). + Operation("create" + kind).Reads(versionedObject) + case "PUT": + route = ws.PUT(path).To(h). + Doc("update the specified " + kind). + Operation("update" + kind).Reads(versionedObject) + case "DELETE": + route = ws.DELETE(path).To(h). + Doc("delete the specified " + kind). + Operation("delete" + kind) + case "GET": + doc := "read the specified " + kind + op := "read" + kind + if pathToStorageVerb[path] == "RESTLister" { + doc = "list objects of kind " + kind + op = "list" + kind + } + route = ws.GET(path).To(h). + Doc(doc). + Operation(op) + if pathToStorageVerb[path] == "RESTLister" { + route = route.Returns(http.StatusOK, "OK", versionedList) + } else { + route = route.Writes(versionedObject) + } + } + for paramIndex := range params { + route.Param(params[paramIndex]) + } + ws.Route(route) + } + } return nil } -// Adds the given param to the given route builder if shouldAdd is true. Does nothing if shouldAdd is false. -func addParamIf(b *restful.RouteBuilder, parameter *restful.Parameter, shouldAdd bool) *restful.RouteBuilder { - if !shouldAdd { - return b +// appendStringIf appends the value to the slice if shouldAdd is true +func appendStringIf(slice []string, value string, shouldAdd bool) []string { + if shouldAdd { + return append(slice, value) } - return b.Param(parameter) + return slice } // InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container. @@ -252,12 +348,7 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, mux Mux, roo registrationErrors := make([]error, 0) for path, storage := range g.handler.storage { - // register legacy patterns where namespace is optional in path - if err := registerResourceHandlers(ws, version, path, storage, h, false); err != nil { - registrationErrors = append(registrationErrors, err) - } - // register pattern where namespace is required in path - if err := registerResourceHandlers(ws, version, path, storage, h, true); err != nil { + if err := registerResourceHandlers(ws, version, path, storage, h, g.mapper); err != nil { registrationErrors = append(registrationErrors, err) } } diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 50840d1aba4..806852886ab 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -93,7 +93,15 @@ func init() { return interfaces, true }, ) - defMapper.Add(api.Scheme, true, versions...) + // enumerate all supported versions, get the kinds, and register with the mapper how to address our resources + for _, version := range versions { + for kind := range api.Scheme.KnownTypes(version) { + mixedCase := true + scope := meta.RESTScopeNamespaceLegacy + defMapper.Add(scope, kind, version, mixedCase) + } + } + mapper = defMapper admissionControl = admit.NewAlwaysAdmit() } @@ -270,7 +278,7 @@ func TestNotFound(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &SimpleRESTStorage{}, - }, codec, "/prefix", testVersion, selfLinker, admissionControl) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -287,6 +295,7 @@ func TestNotFound(t *testing.T) { if response.StatusCode != v.Status { t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response) + t.Errorf("MAPPER: %v", mapper) } } } @@ -297,25 +306,31 @@ func (UnimplementedRESTStorage) New() runtime.Object { return &Simple{} } -func TestMethodNotAllowed(t *testing.T) { +// TestUnimplementedRESTStorage ensures that if a RESTStorage does not implement a given +// method, that it is literally not registered with the server. In the past, +// we registered everything, and returned method not supported if it didn't support +// a verb. Now we literally do not register a storage if it does not implement anything. +// TODO: in future, we should update proxy/redirect +func TestUnimplementedRESTStorage(t *testing.T) { type T struct { - Method string - Path string + Method string + Path string + ErrCode int } cases := map[string]T{ - "GET object": {"GET", "/prefix/version/foo/bar"}, - "GET list": {"GET", "/prefix/version/foo"}, - "POST list": {"POST", "/prefix/version/foo"}, - "PUT object": {"PUT", "/prefix/version/foo/bar"}, - "DELETE object": {"DELETE", "/prefix/version/foo/bar"}, + "GET object": {"GET", "/prefix/version/foo/bar", http.StatusNotFound}, + "GET list": {"GET", "/prefix/version/foo", http.StatusNotFound}, + "POST list": {"POST", "/prefix/version/foo", http.StatusNotFound}, + "PUT object": {"PUT", "/prefix/version/foo/bar", http.StatusNotFound}, + "DELETE object": {"DELETE", "/prefix/version/foo/bar", http.StatusNotFound}, //"watch list": {"GET", "/prefix/version/watch/foo"}, //"watch object": {"GET", "/prefix/version/watch/foo/bar"}, - "proxy object": {"GET", "/prefix/version/proxy/foo/bar"}, - "redirect object": {"GET", "/prefix/version/redirect/foo/bar"}, + "proxy object": {"GET", "/prefix/version/proxy/foo/bar", http.StatusMethodNotAllowed}, + "redirect object": {"GET", "/prefix/version/redirect/foo/bar", http.StatusMethodNotAllowed}, } handler := Handle(map[string]RESTStorage{ "foo": UnimplementedRESTStorage{}, - }, codec, "/prefix", testVersion, selfLinker, admissionControl) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -333,28 +348,15 @@ func TestMethodNotAllowed(t *testing.T) { defer response.Body.Close() data, _ := ioutil.ReadAll(response.Body) t.Logf("resp: %s", string(data)) - if response.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusMethodNotAllowed, v.Method, string(data)) + if response.StatusCode != v.ErrCode { + t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusNotFound, v.Method, string(data)) continue } - obj, err := codec.Decode(data) - if err != nil { - t.Errorf("%s: unexpected decode error: %v", k, err) - continue - } - status, ok := obj.(*api.Status) - if !ok { - t.Errorf("%s: unexpected object: %#v", k, obj) - continue - } - if status.Reason != api.StatusReasonMethodNotAllowed { - t.Errorf("%s: unexpected status: %#v", k, status) - } } } func TestVersion(t *testing.T) { - handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -389,7 +391,7 @@ func TestSimpleList(t *testing.T) { namespace: "other", expectedSet: "/prefix/version/simple?namespace=other", } - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -412,7 +414,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) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -438,7 +440,7 @@ func TestNonEmptyList(t *testing.T) { }, } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -484,7 +486,7 @@ func TestGet(t *testing.T) { expectedSet: "/prefix/version/simple/id", } storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -509,7 +511,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) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -528,7 +530,7 @@ func TestDelete(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -549,7 +551,7 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) { simpleStorage := SimpleRESTStorage{} ID := "id" storage["simple"] = &simpleStorage - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny()) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) server := httptest.NewServer(handler) defer server.Close() @@ -571,7 +573,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) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -596,7 +598,7 @@ func TestUpdate(t *testing.T) { t: t, expectedSet: "/prefix/version/simple/" + ID, } - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -633,7 +635,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) { t: t, expectedSet: "/prefix/version/simple/" + ID, } - handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny()) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) server := httptest.NewServer(handler) defer server.Close() @@ -664,7 +666,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) + handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -695,7 +697,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) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -759,11 +761,11 @@ func TestCreate(t *testing.T) { t: t, name: "bar", namespace: "other", - expectedSet: "/prefix/version/ns/other/foo/bar", + expectedSet: "/prefix/version/foo/bar?namespace=other", } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, admissionControl) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -772,7 +774,7 @@ func TestCreate(t *testing.T) { Other: "bar", } data, _ := codec.Encode(simple) - request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo", bytes.NewBuffer(data)) + request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data)) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -817,11 +819,11 @@ func TestCreateInvokesAdmissionControl(t *testing.T) { t: t, name: "bar", namespace: "other", - expectedSet: "/prefix/version/ns/other/foo/bar", + expectedSet: "/prefix/version/foo/bar?namespace=other", } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny()) + }, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} @@ -830,7 +832,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) { Other: "bar", } data, _ := codec.Encode(simple) - request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo", bytes.NewBuffer(data)) + request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data)) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -881,7 +883,7 @@ func TestDelayReturnsError(t *testing.T) { return nil, apierrs.NewAlreadyExists("foo", "bar") }, } - handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl) + handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -947,7 +949,7 @@ func TestCreateTimeout(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": &storage, - }, codec, "/prefix", testVersion, selfLinker, admissionControl) + }, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() @@ -979,7 +981,7 @@ func TestCORSAllowedOrigins(t *testing.T) { } handler := CORS( - Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl), + Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper), allowedOriginRegexps, nil, nil, "true", ) server := httptest.NewServer(handler) diff --git a/pkg/apiserver/proxy_test.go b/pkg/apiserver/proxy_test.go index 99f3ce66f8d..8eb51e4d09a 100644 --- a/pkg/apiserver/proxy_test.go +++ b/pkg/apiserver/proxy_test.go @@ -230,7 +230,7 @@ func TestProxy(t *testing.T) { } handler := Handle(map[string]RESTStorage{ "foo": simpleStorage, - }, codec, "/prefix", "version", selfLinker, admissionControl) + }, codec, "/prefix", "version", selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() diff --git a/pkg/apiserver/redirect_test.go b/pkg/apiserver/redirect_test.go index c4ae26923b1..2ec95f2a587 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) + }, codec, "/prefix", "version", selfLinker, admissionControl, 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) + }, codec, "/prefix", "version", selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() diff --git a/pkg/apiserver/watch_test.go b/pkg/apiserver/watch_test.go index 276a4d3f5d0..ef33a2c32e0 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) + }, codec, "/api", "version", selfLinker, admissionControl, 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) + }, codec, "/api", "version", selfLinker, admissionControl, 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) + }, codec, "/api", "version", selfLinker, admissionControl, 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) + }, codec, "/api", "version", selfLinker, admissionControl, mapper) server := httptest.NewServer(handler) defer server.Close() defer server.CloseClientConnections() diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index b007a3a30e8..ef5dc0bc674 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -66,7 +66,13 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { MetadataAccessor: meta.NewAccessor(), }, (version == "unlikelyversion") }) - mapper.Add(scheme, false, "unlikelyversion") + for _, version := range []string{"unlikelyversion"} { + for kind := range scheme.KnownTypes(version) { + mixedCase := false + scope := meta.RESTScopeNamespace + mapper.Add(scope, kind, version, mixedCase) + } + } return scheme, mapper, codec } diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 985cc63d8fe..51fa5906469 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -318,7 +318,11 @@ func (b *Builder) visitorResult() *Result { if err != nil { return &Result{err: err} } - visitors = append(visitors, NewSelector(client, mapping, b.namespace, b.selector)) + selectorNamespace := b.namespace + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + selectorNamespace = "" + } + visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector)) } if b.continueOnError { return &Result{visitor: EagerVisitorList(visitors), sources: visitors} @@ -337,14 +341,19 @@ func (b *Builder) visitorResult() *Result { if len(b.resources) > 1 { return &Result{singular: true, err: fmt.Errorf("you must specify only one resource")} } - if len(b.namespace) == 0 { - return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")} - } mappings, err := b.resourceMappings() if err != nil { return &Result{singular: true, err: err} } - client, err := b.mapper.ClientForMapping(mappings[0]) + mapping := mappings[0] + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + b.namespace = "" + } else { + if len(b.namespace) == 0 { + return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")} + } + } + client, err := b.mapper.ClientForMapping(mapping) if err != nil { return &Result{singular: true, err: err} } @@ -402,6 +411,7 @@ func (b *Builder) Do() *Result { if b.requireNamespace { helpers = append(helpers, RequireNamespace(b.namespace)) } + helpers = append(helpers, FilterNamespace()) r.visitor = NewDecoratedVisitor(r.visitor, helpers...) return r } diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 5be98be94d9..99a2094d2ab 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -30,6 +30,7 @@ import ( "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/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" @@ -173,6 +174,37 @@ func TestPathBuilder(t *testing.T) { } } +func TestNodeBuilder(t *testing.T) { + node := &api.Node{ + ObjectMeta: api.ObjectMeta{Name: "node1", Namespace: "should-not-have", ResourceVersion: "10"}, + Spec: api.NodeSpec{ + Capacity: api.ResourceList{ + api.ResourceCPU: resource.MustParse("1000m"), + api.ResourceMemory: resource.MustParse("1Mi"), + }, + }, + } + r, w := io.Pipe() + go func() { + defer w.Close() + w.Write([]byte(runtime.EncodeOrDie(latest.Codec, node))) + }() + + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()). + NamespaceParam("test").Stream(r, "STDIN") + + test := &testVisitor{} + + err := b.Do().Visit(test.Handle) + if err != nil || len(test.Infos) != 1 { + t.Fatalf("unexpected response: %v %#v", err, test.Infos) + } + info := test.Infos[0] + if info.Name != "node1" || info.Namespace != "" || info.Object == nil { + t.Errorf("unexpected info: %#v", info) + } +} + func TestPathBuilderWithMultiple(t *testing.T) { b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()). FilenameParam("../../../examples/guestbook/redis-master.json"). diff --git a/pkg/kubectl/resource/visitor.go b/pkg/kubectl/resource/visitor.go index 368a44cfd27..23f9cf445ec 100644 --- a/pkg/kubectl/resource/visitor.go +++ b/pkg/kubectl/resource/visitor.go @@ -384,6 +384,17 @@ func UpdateObjectNamespace(info *Info) error { return nil } +// FilterNamespace omits the namespace if the object is not namespace scoped +func FilterNamespace() VisitorFunc { + return func(info *Info) error { + if info.Mapping.Scope.Name() != meta.RESTScopeNameNamespace { + info.Namespace = "" + UpdateObjectNamespace(info) + } + return nil + } +} + // SetNamespace ensures that every Info object visited will have a namespace // set. If info.Object is set, it will be mutated as well. func SetNamespace(namespace string) VisitorFunc { diff --git a/pkg/master/master.go b/pkg/master/master.go index cc81000fa52..4687a03e2a2 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -31,6 +31,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/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" @@ -491,25 +492,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) { +func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, 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 + return storage, v1beta1.Codec, "/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) { +func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, 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 + return storage, v1beta2.Codec, "/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) { +func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) { storage := make(map[string]apiserver.RESTStorage) for k, v := range m.storage { if k == "minions" { @@ -517,5 +518,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 + return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper } diff --git a/test/integration/auth_test.go b/test/integration/auth_test.go index a2ca1471291..56b998c7ec6 100644 --- a/test/integration/auth_test.go +++ b/test/integration/auth_test.go @@ -245,10 +245,10 @@ func getTestRequests() []struct { {"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only {"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code200}, // Need a pod to bind or you get a 404 {"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code200}, - {"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code405}, + {"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code404}, {"GET", "/api/v1beta1/bindings", "", code405}, - {"GET", "/api/v1beta1/bindings/a", "", code405}, // No bindings instances - {"DELETE", "/api/v1beta1/bindings/a" + timeoutFlag, "", code405}, + {"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances + {"DELETE", "/api/v1beta1/bindings/a" + timeoutFlag, "", code404}, // Non-existent object type. {"GET", "/api/v1beta1/foo", "", code404},