mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-08 04:32:37 +00:00
Make a RESTMapper scope aware
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user