Make a RESTMapper scope aware

This commit is contained in:
derekwaynecarr
2015-01-29 11:35:06 -05:00
parent e335e2d3e2
commit 71ec444d63
5 changed files with 122 additions and 39 deletions

View File

@@ -103,7 +103,62 @@ func init() {
return interfaces, true return interfaces, true
}, },
) )
mapper.Add(api.Scheme, true, "v1beta1", "v1beta2") // scopes that are used to qualify resources in the API
mapper.Add(api.Scheme, false, "v1beta3") namespaceAsQueryParam := meta.RESTScope{
Name: "namespace",
ParamName: "namespace",
ParamPath: false,
ParamDescription: "object name and auth scope, such as for teams and projects",
}
namespaceAsPathParam := meta.RESTScope{
Name: "namespace",
ParamName: "ns",
ParamPath: true,
ParamDescription: "object name and auth scope, such as for teams and projects",
}
rootScope := meta.RESTScope{
Name: "root",
ParamName: "",
ParamPath: true,
}
// 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": namespaceAsQueryParam,
"v1beta2": namespaceAsQueryParam,
"v1beta3": namespaceAsPathParam,
}
// 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 = rootScope
}
mapper.Add(scope, kind, version, mixedCase)
}
}
RESTMapper = mapper RESTMapper = mapper
} }

View File

@@ -94,6 +94,21 @@ type MetadataAccessor interface {
runtime.ResourceVersioner runtime.ResourceVersioner
} }
// RESTScope contains the information needed to deal with REST Resources that are in a resource hierarchy
type RESTScope struct {
// Name of the scope (e.g. "cluster", "namespace", etc.)
Name string
// 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 // RESTMapping contains the information needed to deal with objects of a specific
// resource and kind in a RESTful manner. // resource and kind in a RESTful manner.
type RESTMapping struct { type RESTMapping struct {
@@ -104,6 +119,9 @@ type RESTMapping struct {
APIVersion string APIVersion string
Kind string Kind string
// Scope contains the information needed to deal with REST Resources that are in a resource hierarchy
Scope RESTScope
runtime.Codec runtime.Codec
runtime.ObjectConvertor runtime.ObjectConvertor
MetadataAccessor MetadataAccessor

View File

@@ -19,8 +19,6 @@ package meta
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
) )
// typeMeta is used as a key for lookup in the mapping between REST path and // typeMeta is used as a key for lookup in the mapping between REST path and
@@ -45,6 +43,7 @@ type typeMeta struct {
type DefaultRESTMapper struct { type DefaultRESTMapper struct {
mapping map[string]typeMeta mapping map[string]typeMeta
reverse map[typeMeta]string reverse map[typeMeta]string
scopes map[typeMeta]RESTScope
versions []string versions []string
interfacesFunc VersionInterfacesFunc interfacesFunc VersionInterfacesFunc
} }
@@ -61,36 +60,31 @@ type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool)
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper { func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
mapping := make(map[string]typeMeta) mapping := make(map[string]typeMeta)
reverse := make(map[typeMeta]string) reverse := make(map[typeMeta]string)
scopes := make(map[typeMeta]RESTScope)
// TODO: verify name mappings work correctly when versions differ // TODO: verify name mappings work correctly when versions differ
return &DefaultRESTMapper{ return &DefaultRESTMapper{
mapping: mapping, mapping: mapping,
reverse: reverse, reverse: reverse,
scopes: scopes,
versions: versions, versions: versions,
interfacesFunc: f, interfacesFunc: f,
} }
} }
// Add adds objects from a runtime.Scheme and its named versions to this map. func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) {
// If mixedCase is true, the legacy v1beta1/v1beta2 Kubernetes resource naming convention plural, singular := kindToResource(kind, mixedCase)
// will be applied (camelCase vs lowercase). meta := typeMeta{APIVersion: version, Kind: kind}
func (m *DefaultRESTMapper) Add(scheme *runtime.Scheme, mixedCase bool, versions ...string) { if _, ok := m.mapping[plural]; !ok {
for _, version := range versions { m.mapping[plural] = meta
for kind := range scheme.KnownTypes(version) { m.mapping[singular] = meta
plural, singular := kindToResource(kind, mixedCase) if strings.ToLower(plural) != plural {
meta := typeMeta{APIVersion: version, Kind: kind} m.mapping[strings.ToLower(plural)] = meta
if _, ok := m.mapping[plural]; !ok { m.mapping[strings.ToLower(singular)] = meta
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.reverse[meta] = plural
m.scopes[meta] = scope
} }
// kindToResource converts Kind to a resource name. // kindToResource converts Kind to a resource name.
@@ -167,6 +161,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) 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) interfaces, ok := m.interfacesFunc(version)
if !ok { if !ok {
return nil, fmt.Errorf("the provided version %q has no relevant versions", version) return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
@@ -176,6 +176,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
Resource: resource, Resource: resource,
APIVersion: version, APIVersion: version,
Kind: kind, Kind: kind,
Scope: scope,
Codec: interfaces.Codec, Codec: interfaces.Codec,
ObjectConvertor: interfaces.ObjectConvertor, ObjectConvertor: interfaces.ObjectConvertor,

View File

@@ -29,6 +29,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@@ -99,7 +100,7 @@ func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(ptrToObject)).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) error {
object := storage.New() object := storage.New()
_, kind, err := api.Scheme.ObjectVersionAndKind(object) _, kind, err := api.Scheme.ObjectVersionAndKind(object)
if err != nil { if err != nil {
@@ -111,21 +112,31 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
} }
versionedObject := indirectArbitraryPointer(versionedPtr) versionedObject := indirectArbitraryPointer(versionedPtr)
mapper := latest.RESTMapper
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
}
// See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic // See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic
// and status-code behavior // and status-code behavior
if namespaceScope { // check if this
path = "ns/{namespace}/" + path scope := mapping.Scope
var scopeParam *restful.Parameter
if len(scope.ParamName) > 0 && scope.ParamPath {
path = scope.ParamName + "/{" + scope.ParamName + "}/" + path
scopeParam = ws.PathParameter(scope.ParamName, scope.ParamDescription).DataType("string")
} }
glog.V(5).Infof("Installing version=/%s, kind=/%s, path=/%s", version, kind, 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") 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). createRoute := ws.POST(path).To(h).
Doc("create a " + kind). Doc("create a " + kind).
Operation("create" + kind) Operation("create" + kind)
addParamIf(createRoute, namespaceParam, namespaceScope) addParamIf(createRoute, scopeParam, scopeParam != nil)
if _, ok := storage.(RESTCreater); ok { if _, ok := storage.(RESTCreater); ok {
ws.Route(createRoute.Reads(versionedObject)) // from the request ws.Route(createRoute.Reads(versionedObject)) // from the request
} else { } else {
@@ -135,7 +146,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
listRoute := ws.GET(path).To(h). listRoute := ws.GET(path).To(h).
Doc("list objects of kind " + kind). Doc("list objects of kind " + kind).
Operation("list" + kind) Operation("list" + kind)
addParamIf(listRoute, namespaceParam, namespaceScope) addParamIf(listRoute, scopeParam, scopeParam != nil)
if lister, ok := storage.(RESTLister); ok { if lister, ok := storage.(RESTLister); ok {
list := lister.NewList() list := lister.NewList()
_, listKind, err := api.Scheme.ObjectVersionAndKind(list) _, listKind, err := api.Scheme.ObjectVersionAndKind(list)
@@ -154,7 +165,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
Doc("read the specified " + kind). Doc("read the specified " + kind).
Operation("read" + kind). Operation("read" + kind).
Param(nameParam) Param(nameParam)
addParamIf(getRoute, namespaceParam, namespaceScope) addParamIf(getRoute, scopeParam, scopeParam != nil)
if _, ok := storage.(RESTGetter); ok { if _, ok := storage.(RESTGetter); ok {
ws.Route(getRoute.Writes(versionedObject)) // on the response ws.Route(getRoute.Writes(versionedObject)) // on the response
} else { } else {
@@ -165,7 +176,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
Doc("update the specified " + kind). Doc("update the specified " + kind).
Operation("update" + kind). Operation("update" + kind).
Param(nameParam) Param(nameParam)
addParamIf(updateRoute, namespaceParam, namespaceScope) addParamIf(updateRoute, scopeParam, scopeParam != nil)
if _, ok := storage.(RESTUpdater); ok { if _, ok := storage.(RESTUpdater); ok {
ws.Route(updateRoute.Reads(versionedObject)) // from the request ws.Route(updateRoute.Reads(versionedObject)) // from the request
} else { } else {
@@ -177,7 +188,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
Doc("delete the specified " + kind). Doc("delete the specified " + kind).
Operation("delete" + kind). Operation("delete" + kind).
Param(nameParam) Param(nameParam)
addParamIf(deleteRoute, namespaceParam, namespaceScope) addParamIf(deleteRoute, scopeParam, scopeParam != nil)
if _, ok := storage.(RESTDeleter); ok { if _, ok := storage.(RESTDeleter); ok {
ws.Route(deleteRoute) ws.Route(deleteRoute)
} else { } else {
@@ -252,12 +263,7 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, mux Mux, roo
registrationErrors := make([]error, 0) registrationErrors := make([]error, 0)
for path, storage := range g.handler.storage { for path, storage := range g.handler.storage {
// register legacy patterns where namespace is optional in path if err := registerResourceHandlers(ws, version, path, storage, h); err != nil {
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 {
registrationErrors = append(registrationErrors, err) registrationErrors = append(registrationErrors, err)
} }
} }

View File

@@ -54,6 +54,9 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
} }
name, _ := mapping.MetadataAccessor.Name(obj) name, _ := mapping.MetadataAccessor.Name(obj)
namespace, _ := mapping.MetadataAccessor.Namespace(obj) namespace, _ := mapping.MetadataAccessor.Namespace(obj)
if mapping.Scope.Name != "namespace" {
namespace = ""
}
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
return &Info{ return &Info{
Mapping: mapping, Mapping: mapping,