mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 09:49:50 +00:00
Define a mapping between REST resource name and kind/apiVersion
Allows clients to abstractly map user input to generic resource paths as per docs/api-conventions.md
This commit is contained in:
parent
f44bb9d673
commit
191c1b975c
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||||
@ -59,23 +60,21 @@ var ResourceVersioner runtime.ResourceVersioner = accessor
|
|||||||
// to go through the InterfacesFor method below.
|
// to go through the InterfacesFor method below.
|
||||||
var SelfLinker runtime.SelfLinker = accessor
|
var SelfLinker runtime.SelfLinker = accessor
|
||||||
|
|
||||||
// VersionInterfaces contains the interfaces one should use for dealing with types of a particular version.
|
// RESTMapper provides the default mapping between REST paths and the objects declared in api.Scheme and all known
|
||||||
type VersionInterfaces struct {
|
// Kubernetes versions.
|
||||||
runtime.Codec
|
var RESTMapper meta.RESTMapper
|
||||||
meta.MetadataAccessor
|
|
||||||
}
|
|
||||||
|
|
||||||
// InterfacesFor returns the default Codec and ResourceVersioner for a given version
|
// InterfacesFor returns the default Codec and ResourceVersioner for a given version
|
||||||
// string, or an error if the version is not known.
|
// 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 {
|
switch version {
|
||||||
case "v1beta1":
|
case "v1beta1":
|
||||||
return &VersionInterfaces{
|
return &meta.VersionInterfaces{
|
||||||
Codec: v1beta1.Codec,
|
Codec: v1beta1.Codec,
|
||||||
MetadataAccessor: accessor,
|
MetadataAccessor: accessor,
|
||||||
}, nil
|
}, nil
|
||||||
case "v1beta2":
|
case "v1beta2":
|
||||||
return &VersionInterfaces{
|
return &meta.VersionInterfaces{
|
||||||
Codec: v1beta2.Codec,
|
Codec: v1beta2.Codec,
|
||||||
MetadataAccessor: accessor,
|
MetadataAccessor: accessor,
|
||||||
}, nil
|
}, 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, ", "))
|
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"
|
"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.
|
// 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
|
// 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
|
// 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
|
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
|
// NewAccessor returns a MetadataAccessor that can retrieve
|
||||||
// or manipulate resource version on objects derived from core API
|
// or manipulate resource version on objects derived from core API
|
||||||
// metadata concepts.
|
// 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