mirror of https://github.com/k3s-io/k3s
Make a RESTMapper scope aware
parent
e335e2d3e2
commit
71ec444d63
|
@ -103,7 +103,62 @@ func init() {
|
|||
return interfaces, true
|
||||
},
|
||||
)
|
||||
mapper.Add(api.Scheme, true, "v1beta1", "v1beta2")
|
||||
mapper.Add(api.Scheme, false, "v1beta3")
|
||||
// scopes that are used to qualify resources in the API
|
||||
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
|
||||
}
|
||||
|
|
|
@ -94,6 +94,21 @@ type MetadataAccessor interface {
|
|||
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
|
||||
// resource and kind in a RESTful manner.
|
||||
type RESTMapping struct {
|
||||
|
@ -104,6 +119,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
|
||||
|
|
|
@ -19,8 +19,6 @@ package meta
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
mapping map[string]typeMeta
|
||||
reverse map[typeMeta]string
|
||||
scopes map[typeMeta]RESTScope
|
||||
versions []string
|
||||
interfacesFunc VersionInterfacesFunc
|
||||
}
|
||||
|
@ -61,36 +60,31 @@ 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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
// 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 +176,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
|
|||
Resource: resource,
|
||||
APIVersion: version,
|
||||
Kind: kind,
|
||||
Scope: scope,
|
||||
|
||||
Codec: interfaces.Codec,
|
||||
ObjectConvertor: interfaces.ObjectConvertor,
|
||||
|
|
|
@ -29,6 +29,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/healthz"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
@ -99,7 +100,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) error {
|
||||
object := storage.New()
|
||||
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
||||
if err != nil {
|
||||
|
@ -111,21 +112,31 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||
}
|
||||
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
|
||||
// and status-code behavior
|
||||
if namespaceScope {
|
||||
path = "ns/{namespace}/" + path
|
||||
// check if this
|
||||
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)
|
||||
|
||||
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)
|
||||
addParamIf(createRoute, scopeParam, scopeParam != nil)
|
||||
if _, ok := storage.(RESTCreater); ok {
|
||||
ws.Route(createRoute.Reads(versionedObject)) // from the request
|
||||
} else {
|
||||
|
@ -135,7 +146,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||
listRoute := ws.GET(path).To(h).
|
||||
Doc("list objects of kind " + kind).
|
||||
Operation("list" + kind)
|
||||
addParamIf(listRoute, namespaceParam, namespaceScope)
|
||||
addParamIf(listRoute, scopeParam, scopeParam != nil)
|
||||
if lister, ok := storage.(RESTLister); ok {
|
||||
list := lister.NewList()
|
||||
_, listKind, err := api.Scheme.ObjectVersionAndKind(list)
|
||||
|
@ -154,7 +165,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||
Doc("read the specified " + kind).
|
||||
Operation("read" + kind).
|
||||
Param(nameParam)
|
||||
addParamIf(getRoute, namespaceParam, namespaceScope)
|
||||
addParamIf(getRoute, scopeParam, scopeParam != nil)
|
||||
if _, ok := storage.(RESTGetter); ok {
|
||||
ws.Route(getRoute.Writes(versionedObject)) // on the response
|
||||
} else {
|
||||
|
@ -165,7 +176,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||
Doc("update the specified " + kind).
|
||||
Operation("update" + kind).
|
||||
Param(nameParam)
|
||||
addParamIf(updateRoute, namespaceParam, namespaceScope)
|
||||
addParamIf(updateRoute, scopeParam, scopeParam != nil)
|
||||
if _, ok := storage.(RESTUpdater); ok {
|
||||
ws.Route(updateRoute.Reads(versionedObject)) // from the request
|
||||
} else {
|
||||
|
@ -177,7 +188,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||
Doc("delete the specified " + kind).
|
||||
Operation("delete" + kind).
|
||||
Param(nameParam)
|
||||
addParamIf(deleteRoute, namespaceParam, namespaceScope)
|
||||
addParamIf(deleteRoute, scopeParam, scopeParam != nil)
|
||||
if _, ok := storage.(RESTDeleter); ok {
|
||||
ws.Route(deleteRoute)
|
||||
} else {
|
||||
|
@ -252,12 +263,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); err != nil {
|
||||
registrationErrors = append(registrationErrors, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
|
|||
}
|
||||
name, _ := mapping.MetadataAccessor.Name(obj)
|
||||
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
|
||||
if mapping.Scope.Name != "namespace" {
|
||||
namespace = ""
|
||||
}
|
||||
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
return &Info{
|
||||
Mapping: mapping,
|
||||
|
|
Loading…
Reference in New Issue