mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #2059 from smarterclayton/rest_mapping
Define a mapping between REST resource name and kind/apiVersion
This commit is contained in:
commit
7c2b7b55e7
@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||
@ -59,23 +60,21 @@ var ResourceVersioner runtime.ResourceVersioner = accessor
|
||||
// to go through the InterfacesFor method below.
|
||||
var SelfLinker runtime.SelfLinker = accessor
|
||||
|
||||
// VersionInterfaces contains the interfaces one should use for dealing with types of a particular version.
|
||||
type VersionInterfaces struct {
|
||||
runtime.Codec
|
||||
meta.MetadataAccessor
|
||||
}
|
||||
// RESTMapper provides the default mapping between REST paths and the objects declared in api.Scheme and all known
|
||||
// Kubernetes versions.
|
||||
var RESTMapper meta.RESTMapper
|
||||
|
||||
// InterfacesFor returns the default Codec and ResourceVersioner for a given version
|
||||
// string, or an error if the version is not known.
|
||||
func InterfacesFor(version string) (*VersionInterfaces, error) {
|
||||
func InterfacesFor(version string) (*meta.VersionInterfaces, error) {
|
||||
switch version {
|
||||
case "v1beta1":
|
||||
return &VersionInterfaces{
|
||||
return &meta.VersionInterfaces{
|
||||
Codec: v1beta1.Codec,
|
||||
MetadataAccessor: accessor,
|
||||
}, nil
|
||||
case "v1beta2":
|
||||
return &VersionInterfaces{
|
||||
return &meta.VersionInterfaces{
|
||||
Codec: v1beta2.Codec,
|
||||
MetadataAccessor: accessor,
|
||||
}, nil
|
||||
@ -83,3 +82,19 @@ func InterfacesFor(version string) (*VersionInterfaces, error) {
|
||||
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
mapper := meta.NewDefaultRESTMapper(
|
||||
Versions,
|
||||
func(version string) (*meta.VersionInterfaces, bool) {
|
||||
interfaces, err := InterfacesFor(version)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return interfaces, true
|
||||
},
|
||||
)
|
||||
mapper.Add(api.Scheme, true, Versions...)
|
||||
// TODO: when v1beta3 is added it will not use mixed case.
|
||||
RESTMapper = mapper
|
||||
}
|
||||
|
@ -186,3 +186,40 @@ func TestInterfacesFor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTMapper(t *testing.T) {
|
||||
if v, k, err := RESTMapper.VersionAndKindForResource("replicationControllers"); err != nil || v != Version || k != "ReplicationController" {
|
||||
t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
|
||||
}
|
||||
if v, k, err := RESTMapper.VersionAndKindForResource("replicationcontrollers"); err != nil || v != Version || k != "ReplicationController" {
|
||||
t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
|
||||
}
|
||||
|
||||
for _, version := range Versions {
|
||||
mapping, err := RESTMapper.RESTMapping(version, "ReplicationController")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" {
|
||||
t.Errorf("incorrect resource name: %#v", mapping)
|
||||
}
|
||||
if mapping.APIVersion != version {
|
||||
t.Errorf("incorrect version: %v", mapping)
|
||||
}
|
||||
|
||||
interfaces, _ := InterfacesFor(version)
|
||||
if mapping.Codec != interfaces.Codec {
|
||||
t.Errorf("unexpected codec: %#v", mapping)
|
||||
}
|
||||
|
||||
rc := &internal.ReplicationController{ObjectMeta: internal.ObjectMeta{Name: "foo"}}
|
||||
name, err := mapping.MetadataAccessor.Name(rc)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if name != "foo" {
|
||||
t.Errorf("unable to retrieve object meta with: %v", mapping.MetadataAccessor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
97
pkg/api/meta/interfaces.go
Normal file
97
pkg/api/meta/interfaces.go
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// VersionInterfaces contains the interfaces one should use for dealing with types of a particular version.
|
||||
type VersionInterfaces struct {
|
||||
runtime.Codec
|
||||
MetadataAccessor
|
||||
}
|
||||
|
||||
// Interface lets you work with object and list metadata from any of the versioned or
|
||||
// internal API objects. Attempting to set or retrieve a field on an object that does
|
||||
// not support that field (Name, UID, Namespace on lists) will be a no-op and return
|
||||
// a default value.
|
||||
type Interface interface {
|
||||
Namespace() string
|
||||
SetNamespace(namespace string)
|
||||
Name() string
|
||||
SetName(name string)
|
||||
UID() string
|
||||
SetUID(uid string)
|
||||
APIVersion() string
|
||||
SetAPIVersion(version string)
|
||||
Kind() string
|
||||
SetKind(kind string)
|
||||
ResourceVersion() string
|
||||
SetResourceVersion(version string)
|
||||
SelfLink() string
|
||||
SetSelfLink(selfLink string)
|
||||
}
|
||||
|
||||
// MetadataAccessor lets you work with object and list metadata from any of the versioned or
|
||||
// internal API objects. Attempting to set or retrieve a field on an object that does
|
||||
// not support that field (Name, UID, Namespace on lists) will be a no-op and return
|
||||
// a default value.
|
||||
//
|
||||
// MetadataAccessor exposes Interface in a way that can be used with multiple objects.
|
||||
type MetadataAccessor interface {
|
||||
APIVersion(obj runtime.Object) (string, error)
|
||||
SetAPIVersion(obj runtime.Object, version string) error
|
||||
|
||||
Kind(obj runtime.Object) (string, error)
|
||||
SetKind(obj runtime.Object, kind string) error
|
||||
|
||||
Namespace(obj runtime.Object) (string, error)
|
||||
SetNamespace(obj runtime.Object, namespace string) error
|
||||
|
||||
Name(obj runtime.Object) (string, error)
|
||||
SetName(obj runtime.Object, name string) error
|
||||
|
||||
UID(obj runtime.Object) (string, error)
|
||||
SetUID(obj runtime.Object, uid string) error
|
||||
|
||||
SelfLink(obj runtime.Object) (string, error)
|
||||
SetSelfLink(obj runtime.Object, selfLink string) error
|
||||
|
||||
runtime.ResourceVersioner
|
||||
}
|
||||
|
||||
// RESTMapping contains the information needed to deal with objects of a specific
|
||||
// resource and kind in a RESTful manner.
|
||||
type RESTMapping struct {
|
||||
// Resource is a string representing the name of this resource as a REST client would see it
|
||||
Resource string
|
||||
// APIVersion represents the APIVersion that represents the resource as presented. It is provided
|
||||
// for convenience for passing around a consistent mapping.
|
||||
APIVersion string
|
||||
|
||||
runtime.Codec
|
||||
MetadataAccessor
|
||||
}
|
||||
|
||||
// RESTMapper allows clients to map resources to kind, and map kind and version
|
||||
// to interfaces for manipulating those objects. It is primarily intended for
|
||||
// consumers of Kubernetes compatible REST APIs as defined in docs/api-conventions.md.
|
||||
type RESTMapper interface {
|
||||
VersionAndKindForResource(resource string) (defaultVersion, kind string, err error)
|
||||
RESTMapping(version, kind string) (*RESTMapping, error)
|
||||
}
|
@ -24,27 +24,6 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Interface lets you work with object and list metadata from any of the versioned or
|
||||
// internal API objects. Attempting to set or retrieve a field on an object that does
|
||||
// not support that field (Name, UID, Namespace on lists) will be a no-op and return
|
||||
// a default value.
|
||||
type Interface interface {
|
||||
Namespace() string
|
||||
SetNamespace(namespace string)
|
||||
Name() string
|
||||
SetName(name string)
|
||||
UID() string
|
||||
SetUID(uid string)
|
||||
APIVersion() string
|
||||
SetAPIVersion(version string)
|
||||
Kind() string
|
||||
SetKind(kind string)
|
||||
ResourceVersion() string
|
||||
SetResourceVersion(version string)
|
||||
SelfLink() string
|
||||
SetSelfLink(selfLink string)
|
||||
}
|
||||
|
||||
// Accessor takes an arbitary object pointer and returns meta.Interface.
|
||||
// obj must be a pointer to an API type. An error is returned if the minimum
|
||||
// required fields are missing. Fields that are not required return the default
|
||||
@ -94,30 +73,6 @@ func Accessor(obj interface{}) (Interface, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// MetadataAccessor lets you work with object metadata from any of the versioned or
|
||||
// internal API objects.
|
||||
type MetadataAccessor interface {
|
||||
APIVersion(obj runtime.Object) (string, error)
|
||||
SetAPIVersion(obj runtime.Object, version string) error
|
||||
|
||||
Kind(obj runtime.Object) (string, error)
|
||||
SetKind(obj runtime.Object, kind string) error
|
||||
|
||||
Namespace(obj runtime.Object) (string, error)
|
||||
SetNamespace(obj runtime.Object, namespace string) error
|
||||
|
||||
Name(obj runtime.Object) (string, error)
|
||||
SetName(obj runtime.Object, name string) error
|
||||
|
||||
UID(obj runtime.Object) (string, error)
|
||||
SetUID(obj runtime.Object, uid string) error
|
||||
|
||||
SelfLink(obj runtime.Object) (string, error)
|
||||
SetSelfLink(obj runtime.Object, selfLink string) error
|
||||
|
||||
runtime.ResourceVersioner
|
||||
}
|
||||
|
||||
// NewAccessor returns a MetadataAccessor that can retrieve
|
||||
// or manipulate resource version on objects derived from core API
|
||||
// metadata concepts.
|
||||
|
165
pkg/api/meta/restmapper.go
Normal file
165
pkg/api/meta/restmapper.go
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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
|
||||
// API object.
|
||||
type typeMeta struct {
|
||||
APIVersion string
|
||||
Kind string
|
||||
}
|
||||
|
||||
// RESTMapper exposes mappings between the types defined in a
|
||||
// runtime.Scheme. It assumes that all types defined the provided scheme
|
||||
// can be mapped with the provided MetadataAccessor and Codec interfaces.
|
||||
//
|
||||
// The resource name of a Kind is defined as the lowercase,
|
||||
// English-plural version of the Kind string in v1beta3 and onwards,
|
||||
// and as the camelCase version of the name in v1beta1 and v1beta2.
|
||||
// When converting from resource to Kind, the singular version of the
|
||||
// resource name is also accepted for convenience.
|
||||
//
|
||||
// TODO: Only accept plural for some operations for increased control?
|
||||
// (`get pod bar` vs `get pods bar`)
|
||||
type DefaultRESTMapper struct {
|
||||
mapping map[string]typeMeta
|
||||
reverse map[typeMeta]string
|
||||
versions []string
|
||||
interfacesFunc VersionInterfacesFunc
|
||||
}
|
||||
|
||||
// VersionInterfacesFunc returns the appropriate codec and metadata accessor for a
|
||||
// given api version, or false if no such api version exists.
|
||||
type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool)
|
||||
|
||||
// NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
|
||||
// to a resource name and back based on the objects in a runtime.Scheme
|
||||
// and the Kubernetes API conventions. Takes a priority list of the versions to
|
||||
// search when an object has no default version (set empty to return an error)
|
||||
// and a function that retrieves the correct codec and metadata for a given version.
|
||||
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
|
||||
mapping := make(map[string]typeMeta)
|
||||
reverse := make(map[typeMeta]string)
|
||||
// TODO: verify name mappings work correctly when versions differ
|
||||
|
||||
return &DefaultRESTMapper{
|
||||
mapping: mapping,
|
||||
reverse: reverse,
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// kindToResource converts Kind to a resource name.
|
||||
func kindToResource(kind string, mixedCase bool) (plural, singular string) {
|
||||
if mixedCase {
|
||||
// Legacy support for mixed case names
|
||||
singular = strings.ToLower(kind[:1]) + kind[1:]
|
||||
} else {
|
||||
singular = strings.ToLower(kind)
|
||||
}
|
||||
if !strings.HasSuffix(singular, "s") {
|
||||
plural = singular + "s"
|
||||
} else {
|
||||
plural = singular
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// VersionAndKindForResource implements RESTMapper
|
||||
func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
|
||||
meta, ok := m.mapping[resource]
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("no resource %q has been defined", resource)
|
||||
}
|
||||
return meta.APIVersion, meta.Kind, nil
|
||||
}
|
||||
|
||||
// RESTMapping returns a struct representing the resource path and conversion interfaces a
|
||||
// RESTClient should use to operate on the provided version and kind. If a version is not
|
||||
// provided, the search order provided to DefaultRESTMapper will be used to resolve which
|
||||
// APIVersion should be used to access the named kind.
|
||||
func (m *DefaultRESTMapper) RESTMapping(version, kind string) (*RESTMapping, error) {
|
||||
// Default to a version with this kind
|
||||
if len(version) == 0 {
|
||||
for _, v := range m.versions {
|
||||
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok {
|
||||
version = v
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(version) == 0 {
|
||||
return nil, fmt.Errorf("no object named %q is registered.", kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have a REST mapping
|
||||
resource, ok := m.reverse[typeMeta{APIVersion: version, Kind: kind}]
|
||||
if !ok {
|
||||
found := []string{}
|
||||
for _, v := range m.versions {
|
||||
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok {
|
||||
found = append(found, v)
|
||||
}
|
||||
}
|
||||
if len(found) > 0 {
|
||||
return nil, fmt.Errorf("object with kind %q exists in versions %q, not %q", kind, strings.Join(found, ", "), version)
|
||||
}
|
||||
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", version, kind)
|
||||
}
|
||||
|
||||
interfaces, ok := m.interfacesFunc(version)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
|
||||
}
|
||||
|
||||
return &RESTMapping{
|
||||
Resource: resource,
|
||||
APIVersion: version,
|
||||
Codec: interfaces.Codec,
|
||||
MetadataAccessor: interfaces.MetadataAccessor,
|
||||
}, nil
|
||||
}
|
216
pkg/api/meta/restmapper_test.go
Normal file
216
pkg/api/meta/restmapper_test.go
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type fakeCodec struct{}
|
||||
|
||||
func (fakeCodec) Encode(runtime.Object) ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (fakeCodec) Decode([]byte) (runtime.Object, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fakeCodec) DecodeInto([]byte, runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var validCodec = fakeCodec{}
|
||||
var validAccessor = resourceAccessor{}
|
||||
|
||||
func fakeInterfaces(version string) (*VersionInterfaces, bool) {
|
||||
return &VersionInterfaces{Codec: validCodec, MetadataAccessor: validAccessor}, true
|
||||
}
|
||||
|
||||
func unmatchedVersionInterfaces(version string) (*VersionInterfaces, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func TestRESTMapperVersionAndKindForResource(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Resource string
|
||||
Kind, APIVersion string
|
||||
MixedCase bool
|
||||
Err bool
|
||||
}{
|
||||
{Resource: "internalobjec", Err: true},
|
||||
{Resource: "internalObjec", Err: true},
|
||||
|
||||
{Resource: "internalobject", Kind: "InternalObject", APIVersion: "test"},
|
||||
{Resource: "internalobjects", Kind: "InternalObject", APIVersion: "test"},
|
||||
|
||||
{Resource: "internalobject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"},
|
||||
{Resource: "internalobjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"},
|
||||
|
||||
{Resource: "internalObject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"},
|
||||
{Resource: "internalObjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces)
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypes("test", &InternalObject{})
|
||||
mapper.Add(scheme, testCase.MixedCase, "test")
|
||||
|
||||
v, k, err := mapper.VersionAndKindForResource(testCase.Resource)
|
||||
hasErr := err != nil
|
||||
if hasErr != testCase.Err {
|
||||
t.Errorf("%d: unexpected error behavior %f: %v", i, testCase.Err, err)
|
||||
continue
|
||||
}
|
||||
if v != testCase.APIVersion || k != testCase.Kind {
|
||||
t.Errorf("%d: unexpected version and kind: %s %s", i, v, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKindToResource(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Kind string
|
||||
MixedCase bool
|
||||
Plural, Singular string
|
||||
}{
|
||||
{Kind: "Pod", MixedCase: true, Plural: "pods", Singular: "pod"},
|
||||
{Kind: "Pod", MixedCase: true, Plural: "pods", Singular: "pod"},
|
||||
{Kind: "Pod", MixedCase: false, Plural: "pods", Singular: "pod"},
|
||||
|
||||
{Kind: "ReplicationController", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"},
|
||||
{Kind: "ReplicationController", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"},
|
||||
// API convention changed with regard to capitalization for v1beta3
|
||||
{Kind: "ReplicationController", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"},
|
||||
|
||||
{Kind: "lowercase", MixedCase: false, Plural: "lowercases", Singular: "lowercase"},
|
||||
// Don't add extra s if the original object is already plural
|
||||
{Kind: "lowercases", MixedCase: false, Plural: "lowercases", Singular: "lowercases"},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
plural, singular := kindToResource(testCase.Kind, testCase.MixedCase)
|
||||
if singular != testCase.Singular || plural != testCase.Plural {
|
||||
t.Errorf("%d: unexpected plural and signular: %s %s", i, plural, singular)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTMapperRESTMapping(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Kind, APIVersion string
|
||||
MixedCase bool
|
||||
|
||||
Resource string
|
||||
Version string
|
||||
Err bool
|
||||
}{
|
||||
{Kind: "Unknown", APIVersion: "", Err: true},
|
||||
|
||||
{Kind: "InternalObject", APIVersion: "test", Resource: "internalobjects"},
|
||||
{Kind: "InternalObject", APIVersion: "test", Resource: "internalobjects"},
|
||||
{Kind: "InternalObject", APIVersion: "", Resource: "internalobjects", Version: "test"},
|
||||
|
||||
{Kind: "InternalObject", APIVersion: "test", Resource: "internalobjects"},
|
||||
{Kind: "InternalObject", APIVersion: "test", MixedCase: true, Resource: "internalObjects"},
|
||||
|
||||
// TODO: add test for a resource that exists in one version but not another
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces)
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypes("test", &InternalObject{})
|
||||
mapper.Add(scheme, testCase.MixedCase, "test")
|
||||
|
||||
mapping, err := mapper.RESTMapping(testCase.APIVersion, testCase.Kind)
|
||||
hasErr := err != nil
|
||||
if hasErr != testCase.Err {
|
||||
t.Errorf("%d: unexpected error behavior %f: %v", i, testCase.Err, err)
|
||||
}
|
||||
if hasErr {
|
||||
continue
|
||||
}
|
||||
if mapping.Resource != testCase.Resource {
|
||||
t.Errorf("%d: unexpected resource: %#v", i, mapping)
|
||||
}
|
||||
version := testCase.Version
|
||||
if version == "" {
|
||||
version = testCase.APIVersion
|
||||
}
|
||||
if mapping.APIVersion != version {
|
||||
t.Errorf("%d: unexpected version: %#v", i, mapping)
|
||||
}
|
||||
if mapping.Codec == nil || mapping.MetadataAccessor == nil {
|
||||
t.Errorf("%d: missing codec and accessor: %#v", i, mapping)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// pick default matching object kind based on search order
|
||||
mapping, err := mapper.RESTMapping("", "OtherObject")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if mapping.Resource != "otherobjects" || mapping.APIVersion != "test2" {
|
||||
t.Errorf("unexpected mapping: %#v", mapping)
|
||||
}
|
||||
|
||||
mapping, err = mapper.RESTMapping("", "InternalObject")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if mapping.Resource != "internalobjects" || mapping.APIVersion != "test1" {
|
||||
t.Errorf("unexpected mapping: %#v", mapping)
|
||||
}
|
||||
|
||||
// mismatch of version
|
||||
mapping, err = mapper.RESTMapping("test2", "InternalObject")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
mapping, err = mapper.RESTMapping("test1", "OtherObject")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
|
||||
// not in the search versions
|
||||
mapping, err = mapper.RESTMapping("test3", "OtherObject")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) {
|
||||
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, unmatchedVersionInterfaces)
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypes("test1", &InternalObject{})
|
||||
mapper.Add(scheme, false, "test1")
|
||||
|
||||
_, err := mapper.RESTMapping("test1", "InternalObject")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user