Merge pull request #17232 from deads2k/gv-restmapping-2

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2015-11-17 03:33:24 -08:00
commit 125ab2cad1
19 changed files with 384 additions and 257 deletions

View File

@ -78,12 +78,16 @@ func TestRESTMapper(t *testing.T) {
t.Errorf("unexpected version mapping: %s %s %v", v, k, err) t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
} }
if m, err := latest.GroupOrDie("").RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.APIVersion != "v1" || m.Resource != "podtemplates" { expectedGroupVersion := unversioned.GroupVersion{Version: "v1"}
if m, err := latest.GroupOrDie("").RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.GroupVersionKind.GroupVersion() != expectedGroupVersion || m.Resource != "podtemplates" {
t.Errorf("unexpected version mapping: %#v %v", m, err) t.Errorf("unexpected version mapping: %#v %v", m, err)
} }
for _, version := range latest.GroupOrDie("").Versions { for _, version := range latest.GroupOrDie("").Versions {
mapping, err := latest.GroupOrDie("").RESTMapper.RESTMapping("ReplicationController", version) currGroupVersion := unversioned.GroupVersion{Version: version}
mapping, err := latest.GroupOrDie("").RESTMapper.RESTMapping("ReplicationController", currGroupVersion.String())
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -91,11 +95,11 @@ func TestRESTMapper(t *testing.T) {
if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" { if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" {
t.Errorf("incorrect resource name: %#v", mapping) t.Errorf("incorrect resource name: %#v", mapping)
} }
if mapping.APIVersion != version { if mapping.GroupVersionKind.GroupVersion() != currGroupVersion {
t.Errorf("incorrect version: %v", mapping) t.Errorf("incorrect version: %v", mapping)
} }
interfaces, _ := latest.GroupOrDie("").InterfacesFor(version) interfaces, _ := latest.GroupOrDie("").InterfacesFor(currGroupVersion.String())
if mapping.Codec != interfaces.Codec { if mapping.Codec != interfaces.Codec {
t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces)
} }

View File

@ -17,9 +17,11 @@ limitations under the License.
package api package api
import ( import (
"fmt"
"strings" "strings"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
) )
@ -33,14 +35,23 @@ func RegisterRESTMapper(m meta.RESTMapper) {
RESTMapper = append(RESTMapper.(meta.MultiRESTMapper), m) RESTMapper = append(RESTMapper.(meta.MultiRESTMapper), m)
} }
func NewDefaultRESTMapper(group string, versions []string, interfacesFunc meta.VersionInterfacesFunc, func NewDefaultRESTMapper(group string, groupVersionStrings []string, interfacesFunc meta.VersionInterfacesFunc,
importPathPrefix string, ignoredKinds, rootScoped sets.String) *meta.DefaultRESTMapper { importPathPrefix string, ignoredKinds, rootScoped sets.String) *meta.DefaultRESTMapper {
mapper := meta.NewDefaultRESTMapper(group, versions, interfacesFunc) mapper := meta.NewDefaultRESTMapper(group, groupVersionStrings, interfacesFunc)
// enumerate all supported versions, get the kinds, and register with the mapper how to address // enumerate all supported versions, get the kinds, and register with the mapper how to address
// our resources. // our resources.
for _, version := range versions { for _, gvString := range groupVersionStrings {
for kind, oType := range Scheme.KnownTypes(version) { gv, err := unversioned.ParseGroupVersion(gvString)
// TODO stop panicing when the types are fixed
if err != nil {
panic(err)
}
if gv.Group != group {
panic(fmt.Sprintf("%q does not match the expect %q", gv.Group, group))
}
for kind, oType := range Scheme.KnownTypes(gv.String()) {
// TODO: Remove import path prefix check. // TODO: Remove import path prefix check.
// We check the import path prefix because we currently stuff both "api" and "extensions" objects // We check the import path prefix because we currently stuff both "api" and "extensions" objects
// into the same group within Scheme since Scheme has no notion of groups yet. // into the same group within Scheme since Scheme has no notion of groups yet.
@ -51,7 +62,7 @@ func NewDefaultRESTMapper(group string, versions []string, interfacesFunc meta.V
if rootScoped.Has(kind) { if rootScoped.Has(kind) {
scope = meta.RESTScopeRoot scope = meta.RESTScopeRoot
} }
mapper.Add(scope, kind, version, false) mapper.Add(scope, kind, gv.String(), false)
} }
} }
return mapper return mapper

View File

@ -17,6 +17,7 @@ limitations under the License.
package meta package meta
import ( import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
) )
@ -124,10 +125,8 @@ type RESTScope interface {
type RESTMapping struct { type RESTMapping struct {
// Resource is a string representing the name of this resource as a REST client would see it // Resource is a string representing the name of this resource as a REST client would see it
Resource string Resource string
// APIVersion represents the APIVersion that represents the resource as presented. It is provided
// for convenience for passing around a consistent mapping. GroupVersionKind unversioned.GroupVersionKind
APIVersion string
Kind string
// Scope contains the information needed to deal with REST Resources that are in a resource hierarchy // Scope contains the information needed to deal with REST Resources that are in a resource hierarchy
Scope RESTScope Scope RESTScope

View File

@ -20,6 +20,8 @@ package meta
import ( import (
"fmt" "fmt"
"strings" "strings"
"k8s.io/kubernetes/pkg/api/unversioned"
) )
// Implements RESTScope interface // Implements RESTScope interface
@ -72,12 +74,12 @@ type typeMeta struct {
// //
// TODO: Only accept plural for some operations for increased control? // TODO: Only accept plural for some operations for increased control?
// (`get pod bar` vs `get pods bar`) // (`get pod bar` vs `get pods bar`)
// TODO these maps should be keyed based on GroupVersionKinds
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 scopes map[typeMeta]RESTScope
group string groupVersions []unversioned.GroupVersion
versions []string
plurals map[string]string plurals map[string]string
singulars map[string]string singulars map[string]string
interfacesFunc VersionInterfacesFunc interfacesFunc VersionInterfacesFunc
@ -92,7 +94,12 @@ type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error)
// and the Kubernetes API conventions. Takes a group name, a priority list of the versions // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
// to search when an object has no default version (set empty to return an error), // 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. // and a function that retrieves the correct codec and metadata for a given version.
func NewDefaultRESTMapper(group string, versions []string, f VersionInterfacesFunc) *DefaultRESTMapper { // TODO remove group when this API is fixed. It is no longer used.
// The external API for a RESTMapper is cross-version and this is currently called using
// group/version tuples. In the end, the structure may be easier to understand with
// a GroupRESTMapper and CrossGroupRESTMapper, but for now, this one is constructed and
// used a CrossGroupRESTMapper.
func NewDefaultRESTMapper(group string, gvStrings []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) scopes := make(map[typeMeta]RESTScope)
@ -100,23 +107,29 @@ func NewDefaultRESTMapper(group string, versions []string, f VersionInterfacesFu
singulars := make(map[string]string) singulars := make(map[string]string)
// TODO: verify name mappings work correctly when versions differ // TODO: verify name mappings work correctly when versions differ
gvs := []unversioned.GroupVersion{}
for _, gvString := range gvStrings {
gvs = append(gvs, unversioned.ParseGroupVersionOrDie(gvString))
}
return &DefaultRESTMapper{ return &DefaultRESTMapper{
mapping: mapping, mapping: mapping,
reverse: reverse, reverse: reverse,
scopes: scopes, scopes: scopes,
group: group, groupVersions: gvs,
versions: versions,
plurals: plurals, plurals: plurals,
singulars: singulars, singulars: singulars,
interfacesFunc: f, interfacesFunc: f,
} }
} }
func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) { func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, gvString string, mixedCase bool) {
gv := unversioned.ParseGroupVersionOrDie(gvString)
plural, singular := KindToResource(kind, mixedCase) plural, singular := KindToResource(kind, mixedCase)
m.plurals[singular] = plural m.plurals[singular] = plural
m.singulars[plural] = singular m.singulars[plural] = singular
meta := typeMeta{APIVersion: version, Kind: kind} meta := typeMeta{APIVersion: gv.String(), Kind: kind}
_, ok1 := m.mapping[plural] _, ok1 := m.mapping[plural]
_, ok2 := m.mapping[strings.ToLower(plural)] _, ok2 := m.mapping[strings.ToLower(plural)]
if !ok1 && !ok2 { if !ok1 && !ok2 {
@ -177,73 +190,89 @@ func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultV
} }
func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) { func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) {
if _, ok := m.mapping[strings.ToLower(resource)]; !ok { typemeta, exists := m.mapping[strings.ToLower(resource)]
if !exists {
return "", fmt.Errorf("in group for resource, no resource %q has been defined", resource) return "", fmt.Errorf("in group for resource, no resource %q has been defined", resource)
} }
return m.group, nil
gv, err := unversioned.ParseGroupVersion(typemeta.APIVersion)
if err != nil {
return "", err
}
return gv.Group, nil
} }
// RESTMapping returns a struct representing the resource path and conversion interfaces a // RESTMapping returns a struct representing the resource path and conversion interfaces a
// RESTClient should use to operate on the provided kind in order of versions. If a version search // RESTClient should use to operate on the provided kind in order of versions. If a version search
// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
// APIVersion should be used to access the named kind. // APIVersion should be used to access the named kind.
// TODO version here in this RESTMapper means just APIVersion, but the RESTMapper API is intended to handle multiple groups
// So this API is broken. The RESTMapper test made it clear that versions here were API versions, but the code tries to use
// them with group/version tuples.
// TODO this should probably become RESTMapping(GroupKind, versions ...string)
func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) { func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) {
// Pick an appropriate version // Pick an appropriate version
var version string var groupVersion *unversioned.GroupVersion
hadVersion := false hadVersion := false
for _, v := range versions { for _, v := range versions {
if len(v) == 0 { if len(v) == 0 {
continue continue
} }
currGroupVersion, err := unversioned.ParseGroupVersion(v)
if err != nil {
return nil, err
}
hadVersion = true hadVersion = true
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { if _, ok := m.reverse[typeMeta{APIVersion: currGroupVersion.String(), Kind: kind}]; ok {
version = v groupVersion = &currGroupVersion
break break
} }
} }
// Use the default preferred versions // Use the default preferred versions
if !hadVersion && len(version) == 0 { if !hadVersion && (groupVersion == nil) {
for _, v := range m.versions { for _, currGroupVersion := range m.groupVersions {
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { if _, ok := m.reverse[typeMeta{APIVersion: currGroupVersion.String(), Kind: kind}]; ok {
version = v groupVersion = &currGroupVersion
break break
} }
} }
} }
if len(version) == 0 { if groupVersion == nil {
return nil, fmt.Errorf("no kind named %q is registered in versions %q", kind, versions) return nil, fmt.Errorf("no kind named %q is registered in versions %q", kind, versions)
} }
gvk := unversioned.NewGroupVersionKind(*groupVersion, kind)
// Ensure we have a REST mapping // Ensure we have a REST mapping
resource, ok := m.reverse[typeMeta{APIVersion: version, Kind: kind}] resource, ok := m.reverse[typeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}]
if !ok { if !ok {
found := []string{} found := []unversioned.GroupVersion{}
for _, v := range m.versions { for _, gv := range m.groupVersions {
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { if _, ok := m.reverse[typeMeta{APIVersion: gv.String(), Kind: kind}]; ok {
found = append(found, v) found = append(found, gv)
} }
} }
if len(found) > 0 { 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("object with kind %q exists in versions %v, not %v", kind, found, *groupVersion)
} }
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", groupVersion, kind)
} }
// Ensure we have a REST scope // Ensure we have a REST scope
scope, ok := m.scopes[typeMeta{APIVersion: version, Kind: kind}] scope, ok := m.scopes[typeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}]
if !ok { if !ok {
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", version, kind) return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
} }
interfaces, err := m.interfacesFunc(version) interfaces, err := m.interfacesFunc(gvk.GroupVersion().String())
if err != nil { if err != nil {
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", gvk.GroupVersion().String())
} }
retVal := &RESTMapping{ retVal := &RESTMapping{
Resource: resource, Resource: resource,
APIVersion: version, GroupVersionKind: gvk,
Kind: kind,
Scope: scope, Scope: scope,
Codec: interfaces.Codec, Codec: interfaces.Codec,

View File

@ -21,6 +21,7 @@ import (
"io" "io"
"testing" "testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
@ -79,35 +80,52 @@ func unmatchedVersionInterfaces(version string) (*VersionInterfaces, error) {
} }
func TestRESTMapperVersionAndKindForResource(t *testing.T) { func TestRESTMapperVersionAndKindForResource(t *testing.T) {
testGroup := "test.group"
testVersion := "test"
testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: testVersion}
testCases := []struct { testCases := []struct {
Resource string Resource string
Kind, APIVersion string GroupVersionToRegister unversioned.GroupVersion
ExpectedGVK unversioned.GroupVersionKind
MixedCase bool MixedCase bool
Err bool Err bool
}{ }{
{Resource: "internalobjec", Err: true}, {Resource: "internalobjec", Err: true, GroupVersionToRegister: testGroupVersion},
{Resource: "internalObjec", Err: true}, {Resource: "internalObjec", Err: true, GroupVersionToRegister: testGroupVersion},
{Resource: "internalobject", Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
{Resource: "internalobjects", Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
{Resource: "internalobject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
{Resource: "internalobjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
{Resource: "internalObject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalObject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
{Resource: "internalObjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalObjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper("tgroup", []string{"test"}, fakeInterfaces) mapper := NewDefaultRESTMapper(testGroup, []string{testGroupVersion.String()}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) mapper.Add(RESTScopeNamespace, testCase.ExpectedGVK.Kind, testCase.GroupVersionToRegister.String(), testCase.MixedCase)
v, k, err := mapper.VersionAndKindForResource(testCase.Resource) v, k, err := mapper.VersionAndKindForResource(testCase.Resource)
hasErr := err != nil hasErr := err != nil
if hasErr != testCase.Err { if hasErr != testCase.Err {
t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err)
continue continue
} }
if v != testCase.APIVersion || k != testCase.Kind { if err != nil {
t.Errorf("%d: unexpected version and kind: %s %s", i, v, k) continue
}
actualGV, err := unversioned.ParseGroupVersion(v)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
actualGVK := unversioned.NewGroupVersionKind(actualGV, k)
if actualGVK != testCase.ExpectedGVK {
t.Errorf("%d: unexpected version and kind: e=%s a=%s", i, testCase.ExpectedGVK, actualGVK)
} }
} }
} }
@ -115,17 +133,18 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) {
func TestRESTMapperGroupForResource(t *testing.T) { func TestRESTMapperGroupForResource(t *testing.T) {
testCases := []struct { testCases := []struct {
Resource string Resource string
Kind, APIVersion, Group string Kind string
GroupVersion unversioned.GroupVersion
Err bool Err bool
}{ }{
{Resource: "myObject", Kind: "MyObject", APIVersion: "test", Group: "testapi"}, {Resource: "myObject", Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}},
{Resource: "myobject", Kind: "MyObject", APIVersion: "test", Group: "testapi2"}, {Resource: "myobject", Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi2", Version: "test"}},
{Resource: "myObje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"}, {Resource: "myObje", Err: true, Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}},
{Resource: "myobje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"}, {Resource: "myobje", Err: true, Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper(testCase.Group, []string{"test"}, fakeInterfaces) mapper := NewDefaultRESTMapper(testCase.GroupVersion.Group, []string{testCase.GroupVersion.String()}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, false) mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.GroupVersion.String(), false)
g, err := mapper.GroupForResource(testCase.Resource) g, err := mapper.GroupForResource(testCase.Resource)
if testCase.Err { if testCase.Err {
if err == nil { if err == nil {
@ -133,8 +152,8 @@ func TestRESTMapperGroupForResource(t *testing.T) {
} }
} else if err != nil { } else if err != nil {
t.Errorf("%d: unexpected error: %v", i, err) t.Errorf("%d: unexpected error: %v", i, err)
} else if g != testCase.Group { } else if g != testCase.GroupVersion.Group {
t.Errorf("%d: expected group %q, got %q", i, testCase.Group, g) t.Errorf("%d: expected group %q, got %q", i, testCase.GroupVersion.Group, g)
} }
} }
} }
@ -170,31 +189,34 @@ func TestKindToResource(t *testing.T) {
func TestRESTMapperResourceSingularizer(t *testing.T) { func TestRESTMapperResourceSingularizer(t *testing.T) {
testCases := []struct { testCases := []struct {
Kind, APIVersion string Kind string
MixedCase bool MixedCase bool
Plural string Plural string
Singular string Singular string
}{ }{
{Kind: "Pod", APIVersion: "test", MixedCase: true, Plural: "pods", Singular: "pod"}, {Kind: "Pod", MixedCase: true, Plural: "pods", Singular: "pod"},
{Kind: "Pod", APIVersion: "test", MixedCase: false, Plural: "pods", Singular: "pod"}, {Kind: "Pod", MixedCase: false, Plural: "pods", Singular: "pod"},
{Kind: "ReplicationController", APIVersion: "test", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"}, {Kind: "ReplicationController", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"},
{Kind: "ReplicationController", APIVersion: "test", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"}, {Kind: "ReplicationController", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"},
{Kind: "ImageRepository", APIVersion: "test", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"}, {Kind: "ImageRepository", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"},
{Kind: "ImageRepository", APIVersion: "test", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"}, {Kind: "ImageRepository", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"},
{Kind: "Status", APIVersion: "test", MixedCase: true, Plural: "statuses", Singular: "status"}, {Kind: "Status", MixedCase: true, Plural: "statuses", Singular: "status"},
{Kind: "Status", APIVersion: "test", MixedCase: false, Plural: "statuses", Singular: "status"}, {Kind: "Status", MixedCase: false, Plural: "statuses", Singular: "status"},
{Kind: "lowercase", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercase"}, {Kind: "lowercase", MixedCase: false, Plural: "lowercases", Singular: "lowercase"},
// Don't add extra s if the original object is already plural // Don't add extra s if the original object is already plural
{Kind: "lowercases", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, {Kind: "lowercases", MixedCase: false, Plural: "lowercases", Singular: "lowercases"},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper("tgroup", []string{"test"}, fakeInterfaces) testGroupVersion := unversioned.GroupVersion{Group: "tgroup", Version: "test"}
mapper := NewDefaultRESTMapper(testGroupVersion.Group, []string{testGroupVersion.String()}, fakeInterfaces)
// create singular/plural mapping // create singular/plural mapping
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) mapper.Add(RESTScopeNamespace, testCase.Kind, testGroupVersion.String(), testCase.MixedCase)
singular, _ := mapper.ResourceSingularizer(testCase.Plural) singular, _ := mapper.ResourceSingularizer(testCase.Plural)
if singular != testCase.Singular { if singular != testCase.Singular {
t.Errorf("%d: mismatched singular: %s, should be %s", i, singular, testCase.Singular) t.Errorf("%d: mismatched singular: %s, should be %s", i, singular, testCase.Singular)
@ -203,37 +225,48 @@ func TestRESTMapperResourceSingularizer(t *testing.T) {
} }
func TestRESTMapperRESTMapping(t *testing.T) { func TestRESTMapperRESTMapping(t *testing.T) {
testGroup := "tgroup"
testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: "test"}
testCases := []struct { testCases := []struct {
Kind string Kind string
APIVersions []string APIGroupVersions []unversioned.GroupVersion
MixedCase bool MixedCase bool
DefaultVersions []string DefaultVersions []string
Resource string Resource string
Version string ExpectedGroupVersion *unversioned.GroupVersion
Err bool Err bool
}{ }{
{Kind: "Unknown", Err: true}, {Kind: "Unknown", Err: true},
{Kind: "InternalObject", Err: true}, {Kind: "InternalObject", Err: true},
{DefaultVersions: []string{"test"}, Kind: "Unknown", Err: true}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "Unknown", Err: true},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, Resource: "internalobjects"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: "internalobjects"},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, Resource: "internalobjects"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: "internalobjects"},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, Resource: "internalobjects"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: "internalobjects"},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{}, Resource: "internalobjects", Version: "test"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{}, Resource: "internalobjects", ExpectedGroupVersion: &unversioned.GroupVersion{Group: testGroup, Version: "test"}},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, Resource: "internalobjects"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: "internalobjects"},
{DefaultVersions: []string{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, MixedCase: true, Resource: "internalObjects"}, {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, MixedCase: true, Resource: "internalObjects"},
// TODO: add test for a resource that exists in one version but not another // TODO: add test for a resource that exists in one version but not another
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper("tgroup", testCase.DefaultVersions, fakeInterfaces) internalGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: "test"}
mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase)
mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...) mapper := NewDefaultRESTMapper(testGroup, testCase.DefaultVersions, fakeInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", internalGroupVersion.String(), testCase.MixedCase)
deprecatedGroupVersionStrings := []string{}
for _, gv := range testCase.APIGroupVersions {
deprecatedGroupVersionStrings = append(deprecatedGroupVersionStrings, gv.String())
}
mapping, err := mapper.RESTMapping(testCase.Kind, deprecatedGroupVersionStrings...)
hasErr := err != nil hasErr := err != nil
if hasErr != testCase.Err { if hasErr != testCase.Err {
t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err)
@ -244,30 +277,37 @@ func TestRESTMapperRESTMapping(t *testing.T) {
if mapping.Resource != testCase.Resource { if mapping.Resource != testCase.Resource {
t.Errorf("%d: unexpected resource: %#v", i, mapping) t.Errorf("%d: unexpected resource: %#v", i, mapping)
} }
version := testCase.Version
if version == "" {
version = testCase.APIVersions[0]
}
if mapping.APIVersion != version {
t.Errorf("%d: unexpected version: %#v", i, mapping)
}
if mapping.Codec == nil || mapping.MetadataAccessor == nil || mapping.ObjectConvertor == nil { if mapping.Codec == nil || mapping.MetadataAccessor == nil || mapping.ObjectConvertor == nil {
t.Errorf("%d: missing codec and accessor: %#v", i, mapping) t.Errorf("%d: missing codec and accessor: %#v", i, mapping)
} }
groupVersion := testCase.ExpectedGroupVersion
if groupVersion == nil {
groupVersion = &testCase.APIGroupVersions[0]
}
if mapping.GroupVersionKind.GroupVersion() != *groupVersion {
t.Errorf("%d: unexpected version: %#v", i, mapping)
}
} }
} }
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, fakeInterfaces) expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"}
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"}
mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false) expectedGroupVersion3 := unversioned.GroupVersion{Group: "tgroup", Version: "test3"}
mapper := NewDefaultRESTMapper("tgroup", []string{expectedGroupVersion1.String(), expectedGroupVersion2.String()}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", expectedGroupVersion1.String(), false)
mapper.Add(RESTScopeNamespace, "OtherObject", expectedGroupVersion2.String(), false)
// pick default matching object kind based on search order // pick default matching object kind based on search order
mapping, err := mapper.RESTMapping("OtherObject") mapping, err := mapper.RESTMapping("OtherObject")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if mapping.Resource != "otherobjects" || mapping.APIVersion != "test2" { if mapping.Resource != "otherobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 {
t.Errorf("unexpected mapping: %#v", mapping) t.Errorf("unexpected mapping: %#v", mapping)
} }
@ -275,45 +315,48 @@ func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if mapping.Resource != "internalobjects" || mapping.APIVersion != "test1" { if mapping.Resource != "internalobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion1 {
t.Errorf("unexpected mapping: %#v", mapping) t.Errorf("unexpected mapping: %#v", mapping)
} }
// mismatch of version // mismatch of version
mapping, err = mapper.RESTMapping("InternalObject", "test2") mapping, err = mapper.RESTMapping("InternalObject", expectedGroupVersion2.String())
if err == nil { if err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
mapping, err = mapper.RESTMapping("OtherObject", "test1") mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion1.String())
if err == nil { if err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
// not in the search versions // not in the search versions
mapping, err = mapper.RESTMapping("OtherObject", "test3") mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String())
if err == nil { if err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
// explicit search order // explicit search order
mapping, err = mapper.RESTMapping("OtherObject", "test3", "test1") mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion1.String())
if err == nil { if err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
mapping, err = mapper.RESTMapping("OtherObject", "test3", "test2") mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion2.String())
if err != nil { if err != nil {
t.Fatalf("unexpected non-error") t.Fatalf("unexpected error: %v", err)
} }
if mapping.Resource != "otherobjects" || mapping.APIVersion != "test2" { if mapping.Resource != "otherobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 {
t.Errorf("unexpected mapping: %#v", mapping) t.Errorf("unexpected mapping: %#v", mapping)
} }
} }
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) {
mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, unmatchedVersionInterfaces) expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"}
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"}
_, err := mapper.RESTMapping("InternalObject", "test1")
mapper := NewDefaultRESTMapper("tgroup", []string{expectedGroupVersion1.String(), expectedGroupVersion2.String()}, unmatchedVersionInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", expectedGroupVersion1.String(), false)
_, err := mapper.RESTMapping("InternalObject", expectedGroupVersion1.String())
if err == nil { if err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }

View File

@ -22,7 +22,26 @@ import (
"strings" "strings"
) )
// TODO: We need to remove the GroupVersion in types.go. We use the name GroupVersion here temporarily. // GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion
// to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling
type GroupVersionKind struct {
Group string
Version string
Kind string
}
func NewGroupVersionKind(gv GroupVersion, kind string) GroupVersionKind {
return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
}
func (gvk GroupVersionKind) GroupVersion() GroupVersion {
return GroupVersion{Group: gvk.Group, Version: gvk.Version}
}
func (gvk *GroupVersionKind) String() string {
return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind
}
// GroupVersion contains the "group" and the "version", which uniquely identifies the API. // GroupVersion contains the "group" and the "version", which uniquely identifies the API.
type GroupVersion struct { type GroupVersion struct {
Group string Group string
@ -31,7 +50,7 @@ type GroupVersion struct {
// String puts "group" and "version" into a single "group/version" string. For the legacy v1 // String puts "group" and "version" into a single "group/version" string. For the legacy v1
// it returns "v1". // it returns "v1".
func (gv *GroupVersion) String() string { func (gv GroupVersion) String() string {
// special case of "v1" for backward compatibility // special case of "v1" for backward compatibility
if gv.Group == "" && gv.Version == "v1" { if gv.Group == "" && gv.Version == "v1" {
return gv.Version return gv.Version

View File

@ -21,6 +21,7 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api/latest" "k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/apis/componentconfig"
) )
@ -53,28 +54,36 @@ func TestInterfacesFor(t *testing.T) {
} }
func TestRESTMapper(t *testing.T) { func TestRESTMapper(t *testing.T) {
if v, k, err := latest.GroupOrDie("componentconfig").RESTMapper.VersionAndKindForResource("kubeproxyconfiguration"); err != nil || v != "componentconfig/v1alpha1" || k != "KubeProxyConfiguration" { expectedGroupVersion := unversioned.GroupVersion{Group: "componentconfig", Version: "v1alpha1"}
t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
if v, k, err := latest.GroupOrDie("componentconfig").RESTMapper.VersionAndKindForResource("kubeproxyconfiguration"); err != nil || v != expectedGroupVersion.String() || k != "KubeProxyConfiguration" {
t.Errorf("unexpected version mapping: %q %q %v", v, k, err)
} }
if m, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping("KubeProxyConfiguration", ""); err != nil || m.APIVersion != "componentconfig/v1alpha1" || m.Resource != "kubeproxyconfigurations" { if m, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping("KubeProxyConfiguration", ""); err != nil || m.GroupVersionKind.GroupVersion() != expectedGroupVersion || m.Resource != "kubeproxyconfigurations" {
t.Errorf("unexpected version mapping: %#v %v", m, err) t.Errorf("unexpected version mapping: %#v %v", m, err)
} }
for _, groupVersion := range latest.GroupOrDie("componentconfig").GroupVersions { for _, groupVersionString := range latest.GroupOrDie("componentconfig").GroupVersions {
mapping, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping("KubeProxyConfiguration", groupVersion) gv, err := unversioned.ParseGroupVersion(groupVersionString)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
continue
}
mapping, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping("KubeProxyConfiguration", gv.String())
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
} }
if mapping.Resource != "kubeproxyconfigurations" { if mapping.Resource != "kubeproxyconfigurations" {
t.Errorf("incorrect resource name: %#v", mapping) t.Errorf("incorrect resource name: %#v", mapping)
} }
if mapping.APIVersion != groupVersion { if mapping.GroupVersionKind.GroupVersion() != gv {
t.Errorf("incorrect groupVersion: %v", mapping) t.Errorf("incorrect groupVersion: %v", mapping)
} }
interfaces, _ := latest.GroupOrDie("componentconfig").InterfacesFor(groupVersion) interfaces, _ := latest.GroupOrDie("componentconfig").InterfacesFor(gv.String())
if mapping.Codec != interfaces.Codec { if mapping.Codec != interfaces.Codec {
t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces)
} }

View File

@ -75,16 +75,20 @@ func TestInterfacesFor(t *testing.T) {
} }
func TestRESTMapper(t *testing.T) { func TestRESTMapper(t *testing.T) {
if v, k, err := latest.GroupOrDie("extensions").RESTMapper.VersionAndKindForResource("horizontalpodautoscalers"); err != nil || v != "extensions/v1beta1" || k != "HorizontalPodAutoscaler" { expectedGroupVersion := unversioned.GroupVersion{Group: "extensions", Version: "v1beta1"}
if v, k, err := latest.GroupOrDie("extensions").RESTMapper.VersionAndKindForResource("horizontalpodautoscalers"); err != nil || v != expectedGroupVersion.String() || k != "HorizontalPodAutoscaler" {
t.Errorf("unexpected version mapping: %s %s %v", v, k, err) t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
} }
if m, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("DaemonSet", ""); err != nil || m.APIVersion != "extensions/v1beta1" || m.Resource != "daemonsets" { if m, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("DaemonSet", ""); err != nil || m.GroupVersionKind.GroupVersion() != expectedGroupVersion || m.Resource != "daemonsets" {
t.Errorf("unexpected version mapping: %#v %v", m, err) t.Errorf("unexpected version mapping: %#v %v", m, err)
} }
for _, groupVersion := range latest.GroupOrDie("extensions").GroupVersions { for _, groupVersionString := range latest.GroupOrDie("extensions").GroupVersions {
mapping, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("HorizontalPodAutoscaler", groupVersion) gv, err := unversioned.ParseGroupVersion(groupVersionString)
mapping, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("HorizontalPodAutoscaler", gv.String())
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -92,11 +96,11 @@ func TestRESTMapper(t *testing.T) {
if mapping.Resource != "horizontalpodautoscalers" { if mapping.Resource != "horizontalpodautoscalers" {
t.Errorf("incorrect resource name: %#v", mapping) t.Errorf("incorrect resource name: %#v", mapping)
} }
if mapping.APIVersion != groupVersion { if mapping.GroupVersionKind.GroupVersion() != gv {
t.Errorf("incorrect groupVersion: %v", mapping) t.Errorf("incorrect groupVersion: %v", mapping)
} }
interfaces, _ := latest.GroupOrDie("extensions").InterfacesFor(groupVersion) interfaces, _ := latest.GroupOrDie("extensions").InterfacesFor(gv.String())
if mapping.Codec != interfaces.Codec { if mapping.Codec != interfaces.Codec {
t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces)
} }

View File

@ -59,19 +59,16 @@ func convert(obj runtime.Object) (runtime.Object, error) {
// This creates fake API versions, similar to api/latest.go. // This creates fake API versions, similar to api/latest.go.
var testAPIGroup = "test.group" var testAPIGroup = "test.group"
var testGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version"} var testGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version"}
var testVersion = testGroupVersion.String()
var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version2"} var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version2"}
var newVersion = newGroupVersion.String()
var prefix = "apis" var prefix = "apis"
var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"} var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"}
var grouplessVersion = grouplessGroupVersion.String()
var grouplessPrefix = "api" var grouplessPrefix = "api"
var grouplessCodec = runtime.CodecFor(api.Scheme, grouplessVersion) var grouplessCodec = runtime.CodecFor(api.Scheme, grouplessGroupVersion.String())
var versions = []string{grouplessVersion, testVersion, newVersion} var groupVersions = []unversioned.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion}
var codec = runtime.CodecFor(api.Scheme, testVersion) var codec = runtime.CodecFor(api.Scheme, testGroupVersion.String())
var newCodec = runtime.CodecFor(api.Scheme, newVersion) var newCodec = runtime.CodecFor(api.Scheme, newGroupVersion.String())
var accessor = meta.NewAccessor() var accessor = meta.NewAccessor()
var versioner runtime.ResourceVersioner = accessor var versioner runtime.ResourceVersioner = accessor
@ -81,32 +78,37 @@ var admissionControl admission.Interface
var requestContextMapper api.RequestContextMapper var requestContextMapper api.RequestContextMapper
func interfacesFor(version string) (*meta.VersionInterfaces, error) { func interfacesFor(version string) (*meta.VersionInterfaces, error) {
switch version { gv, err := unversioned.ParseGroupVersion(version)
case testVersion: if err != nil {
return nil, err
}
switch gv {
case testGroupVersion:
return &meta.VersionInterfaces{ return &meta.VersionInterfaces{
Codec: codec, Codec: codec,
ObjectConvertor: api.Scheme, ObjectConvertor: api.Scheme,
MetadataAccessor: accessor, MetadataAccessor: accessor,
}, nil }, nil
case newVersion: case newGroupVersion:
return &meta.VersionInterfaces{ return &meta.VersionInterfaces{
Codec: newCodec, Codec: newCodec,
ObjectConvertor: api.Scheme, ObjectConvertor: api.Scheme,
MetadataAccessor: accessor, MetadataAccessor: accessor,
}, nil }, nil
case grouplessVersion: case grouplessGroupVersion:
return &meta.VersionInterfaces{ return &meta.VersionInterfaces{
Codec: grouplessCodec, Codec: grouplessCodec,
ObjectConvertor: api.Scheme, ObjectConvertor: api.Scheme,
MetadataAccessor: accessor, MetadataAccessor: accessor,
}, nil }, nil
default: default:
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(versions, ", ")) return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, groupVersions)
} }
} }
func newMapper() *meta.DefaultRESTMapper { func newMapper() *meta.DefaultRESTMapper {
return meta.NewDefaultRESTMapper("testgroup", versions, interfacesFor) gvStrings := []string{testGroupVersion.String(), newGroupVersion.String()}
return meta.NewDefaultRESTMapper(testAPIGroup, gvStrings, interfacesFor)
} }
func addGrouplessTypes() { func addGrouplessTypes() {
@ -120,9 +122,9 @@ func addGrouplessTypes() {
TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"`
} }
api.Scheme.AddKnownTypes( api.Scheme.AddKnownTypes(
grouplessVersion, &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{}, grouplessGroupVersion.String(), &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{},
&ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) &ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{})
api.Scheme.AddKnownTypes(grouplessVersion, &api.Pod{}) api.Scheme.AddKnownTypes(grouplessGroupVersion.String(), &api.Pod{})
} }
func addTestTypes() { func addTestTypes() {
@ -136,9 +138,9 @@ func addTestTypes() {
TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"`
} }
api.Scheme.AddKnownTypes( api.Scheme.AddKnownTypes(
testVersion, &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{}, testGroupVersion.String(), &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{},
&ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) &ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{})
api.Scheme.AddKnownTypes(testVersion, &api.Pod{}) api.Scheme.AddKnownTypes(testGroupVersion.String(), &api.Pod{})
} }
func addNewTestTypes() { func addNewTestTypes() {
@ -152,7 +154,7 @@ func addNewTestTypes() {
TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"`
} }
api.Scheme.AddKnownTypes( api.Scheme.AddKnownTypes(
newVersion, &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{}, newGroupVersion.String(), &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &unversioned.Status{},
&ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) &ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{})
} }
@ -172,13 +174,13 @@ func init() {
// enumerate all supported versions, get the kinds, and register with // enumerate all supported versions, get the kinds, and register with
// the mapper how to address our resources // the mapper how to address our resources
for _, version := range versions { for _, gv := range groupVersions {
for kind := range api.Scheme.KnownTypes(version) { for kind := range api.Scheme.KnownTypes(gv.String()) {
root := bool(kind == "SimpleRoot") root := bool(kind == "SimpleRoot")
if root { if root {
nsMapper.Add(meta.RESTScopeRoot, kind, version, false) nsMapper.Add(meta.RESTScopeRoot, kind, gv.String(), false)
} else { } else {
nsMapper.Add(meta.RESTScopeNamespace, kind, version, false) nsMapper.Add(meta.RESTScopeNamespace, kind, gv.String(), false)
} }
} }
} }
@ -188,17 +190,17 @@ func init() {
admissionControl = admit.NewAlwaysAdmit() admissionControl = admit.NewAlwaysAdmit()
requestContextMapper = api.NewRequestContextMapper() requestContextMapper = api.NewRequestContextMapper()
api.Scheme.AddFieldLabelConversionFunc(grouplessVersion, "Simple", api.Scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.String(), "Simple",
func(label, value string) (string, string, error) { func(label, value string) (string, string, error) {
return label, value, nil return label, value, nil
}, },
) )
api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple", api.Scheme.AddFieldLabelConversionFunc(testGroupVersion.String(), "Simple",
func(label, value string) (string, string, error) { func(label, value string) (string, string, error) {
return label, value, nil return label, value, nil
}, },
) )
api.Scheme.AddFieldLabelConversionFunc(newVersion, "Simple", api.Scheme.AddFieldLabelConversionFunc(newGroupVersion.String(), "Simple",
func(label, value string) (string, string, error) { func(label, value string) (string, string, error) {
return label, value, nil return label, value, nil
}, },
@ -653,31 +655,31 @@ func TestNotFound(t *testing.T) {
} }
cases := map[string]T{ cases := map[string]T{
// Positive checks to make sure everything is wired correctly // Positive checks to make sure everything is wired correctly
"groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusOK}, "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK},
"groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusOK}, "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK},
"groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
"groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
"groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
"groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
"groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed},
"groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound},
"groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound},
"groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, "groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, "groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound},
"groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound},
"groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
"groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed},
"groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound},
"groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound},
"groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed},
// Positive checks to make sure everything is wired correctly // Positive checks to make sure everything is wired correctly
"GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK},
@ -753,15 +755,15 @@ func TestUnimplementedRESTStorage(t *testing.T) {
ErrCode int ErrCode int
} }
cases := map[string]T{ cases := map[string]T{
"groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
"groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound},
"groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound},
"groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
"groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound},
"groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo", http.StatusNotFound}, "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound},
"groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo/bar", http.StatusNotFound}, "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound},
"groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/proxy/foo/bar", http.StatusNotFound}, "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound},
"groupless redirect object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/redirect/foo/bar", http.StatusNotFound}, "groupless redirect object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/redirect/foo/bar", http.StatusNotFound},
"GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, "GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound},
"GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, "GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound},
@ -841,76 +843,76 @@ func TestList(t *testing.T) {
// legacy namespace param is ignored // legacy namespace param is ignored
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
legacy: true, legacy: true,
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=other", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
legacy: true, legacy: true,
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
legacy: true, legacy: true,
label: "a=b", label: "a=b",
field: "c=d", field: "c=d",
}, },
// legacy api version is honored // legacy api version is honored
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
legacy: true, legacy: true,
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
namespace: "other", namespace: "other",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
legacy: true, legacy: true,
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
namespace: "other", namespace: "other",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
legacy: true, legacy: true,
label: "a=b", label: "a=b",
field: "c=d", field: "c=d",
}, },
// list items across all namespaces // list items across all namespaces
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
legacy: true, legacy: true,
}, },
// list items in a namespace in the path // list items in a namespace in the path
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/default/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
namespace: "default", namespace: "default",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/default/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
namespace: "other", namespace: "other",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
}, },
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
namespace: "other", namespace: "other",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple",
label: "a=b", label: "a=b",
field: "c=d", field: "c=d",
}, },
// list items across all namespaces // list items across all namespaces
{ {
url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
namespace: "", namespace: "",
selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple",
}, },
// Group API // Group API
@ -2862,7 +2864,7 @@ func TestCreateChecksAPIVersion(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testVersion+"/namespaces/default/simple", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -2903,7 +2905,7 @@ func TestCreateDefaultsAPIVersion(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testVersion+"/namespaces/default/simple", bytes.NewBuffer(data)) request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -2927,7 +2929,7 @@ func TestUpdateChecksAPIVersion(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testVersion+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data))
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }

View File

@ -178,8 +178,8 @@ func TestWatchParamParsing(t *testing.T) {
dest, _ := url.Parse(server.URL) dest, _ := url.Parse(server.URL)
rootPath := "/" + prefix + "/" + testVersion + "/watch/simples" rootPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
namespacedPath := "/" + prefix + "/" + testVersion + "/watch/namespaces/other/simpleroots" namespacedPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/other/simpleroots"
table := []struct { table := []struct {
path string path string
@ -358,13 +358,13 @@ func TestWatchHTTPTimeout(t *testing.T) {
// Setup a client // Setup a client
dest, _ := url.Parse(s.URL) dest, _ := url.Parse(s.URL)
dest.Path = "/" + prefix + "/" + newVersion + "/simple" dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple"
dest.RawQuery = "watch=true" dest.RawQuery = "watch=true"
req, _ := http.NewRequest("GET", dest.String(), nil) req, _ := http.NewRequest("GET", dest.String(), nil)
client := http.Client{} client := http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
watcher.Add(&apiservertesting.Simple{TypeMeta: unversioned.TypeMeta{APIVersion: newVersion}}) watcher.Add(&apiservertesting.Simple{TypeMeta: unversioned.TypeMeta{APIVersion: newGroupVersion.String()}})
// Make sure we can actually watch an endpoint // Make sure we can actually watch an endpoint
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)

View File

@ -96,7 +96,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
} }
info := infos[0] info := infos[0]
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
if err := f.CanBeAutoscaled(mapping.Kind); err != nil { if err := f.CanBeAutoscaled(mapping.GroupVersionKind.Kind); err != nil {
return err return err
} }
@ -111,9 +111,9 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
name := info.Name name := info.Name
params["default-name"] = name params["default-name"] = name
params["scaleRef-kind"] = mapping.Kind params["scaleRef-kind"] = mapping.GroupVersionKind.Kind
params["scaleRef-name"] = name params["scaleRef-name"] = name
params["scaleRef-apiVersion"] = mapping.APIVersion params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String()
if err = kubectl.ValidateParams(names, params); err != nil { if err = kubectl.ValidateParams(names, params); err != nil {
return err return err

View File

@ -77,23 +77,27 @@ func versionErrIfFalse(b bool) error {
return versionErr return versionErr
} }
var validVersion = testapi.Default.Version()
var internalGV = unversioned.GroupVersion{Group: "apitest", Version: ""}
var unlikelyGV = unversioned.GroupVersion{Group: "apitest", Version: "unlikelyversion"}
var validVersionGV = unversioned.GroupVersion{Group: "apitest", Version: validVersion}
func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName("", "Type", &internalType{}) scheme.AddKnownTypeWithName(internalGV.Version, "Type", &internalType{})
scheme.AddKnownTypeWithName("unlikelyversion", "Type", &externalType{}) scheme.AddKnownTypeWithName(unlikelyGV.String(), "Type", &externalType{})
//This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
scheme.AddKnownTypeWithName(testapi.Default.Version(), "Type", &ExternalType2{}) scheme.AddKnownTypeWithName(validVersionGV.String(), "Type", &ExternalType2{})
codec := runtime.CodecFor(scheme, "unlikelyversion") codec := runtime.CodecFor(scheme, unlikelyGV.String())
validVersion := testapi.Default.Version() mapper := meta.NewDefaultRESTMapper("apitest", []string{unlikelyGV.String(), validVersionGV.String()}, func(version string) (*meta.VersionInterfaces, error) {
mapper := meta.NewDefaultRESTMapper("apitest", []string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, error) {
return &meta.VersionInterfaces{ return &meta.VersionInterfaces{
Codec: runtime.CodecFor(scheme, version), Codec: runtime.CodecFor(scheme, version),
ObjectConvertor: scheme, ObjectConvertor: scheme,
MetadataAccessor: meta.NewAccessor(), MetadataAccessor: meta.NewAccessor(),
}, versionErrIfFalse(version == validVersion || version == "unlikelyversion") }, versionErrIfFalse(version == validVersionGV.String() || version == unlikelyGV.String())
}) })
for _, version := range []string{"unlikelyversion", validVersion} { for _, version := range []string{unlikelyGV.String(), validVersionGV.String()} {
for kind := range scheme.KnownTypes(version) { for kind := range scheme.KnownTypes(version) {
mixedCase := false mixedCase := false
scope := meta.RESTScopeNamespace scope := meta.RESTScopeNamespace

View File

@ -120,7 +120,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
} }
info := infos[0] info := infos[0]
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
if err := f.CanBeExposed(mapping.Kind); err != nil { if err := f.CanBeExposed(mapping.GroupVersionKind.Kind); err != nil {
return err return err
} }
// Get the input object // Get the input object
@ -187,7 +187,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
} }
if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 { if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 {
object, err = cmdutil.Merge(object, inline, mapping.Kind) object, err = cmdutil.Merge(object, inline, mapping.GroupVersionKind.Kind)
if err != nil { if err != nil {
return err return err
} }

View File

@ -159,19 +159,19 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) {
"handles specific version": { "handles specific version": {
outputVersion: testapi.Default.Version(), outputVersion: testapi.Default.Version(),
listVersion: testapi.Default.Version(), listVersion: testapi.Default.Version(),
testtypeVersion: "unlikelyversion", testtypeVersion: unlikelyGV.String(),
rcVersion: testapi.Default.Version(), rcVersion: testapi.Default.Version(),
}, },
"handles second specific version": { "handles second specific version": {
outputVersion: "unlikelyversion", outputVersion: "unlikelyversion",
listVersion: testapi.Default.Version(), listVersion: testapi.Default.Version(),
testtypeVersion: "unlikelyversion", testtypeVersion: unlikelyGV.String(),
rcVersion: testapi.Default.Version(), // see expected behavior 3b rcVersion: testapi.Default.Version(), // see expected behavior 3b
}, },
"handles common version": { "handles common version": {
outputVersion: testapi.Default.Version(), outputVersion: testapi.Default.Version(),
listVersion: testapi.Default.Version(), listVersion: testapi.Default.Version(),
testtypeVersion: "unlikelyversion", testtypeVersion: unlikelyGV.String(),
rcVersion: testapi.Default.Version(), rcVersion: testapi.Default.Version(),
}, },
} }
@ -198,6 +198,7 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) {
cmd := NewCmdGet(f, buf) cmd := NewCmdGet(f, buf)
cmd.SetOutput(buf) cmd.SetOutput(buf)
cmd.Flags().Set("output", "json") cmd.Flags().Set("output", "json")
cmd.Flags().Set("output-version", test.outputVersion) cmd.Flags().Set("output-version", test.outputVersion)
err := RunGet(f, buf, cmd, []string{"type/foo", "replicationcontrollers/foo"}, &GetOptions{}) err := RunGet(f, buf, cmd, []string{"type/foo", "replicationcontrollers/foo"}, &GetOptions{})
if err != nil { if err != nil {

View File

@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -382,8 +383,9 @@ func isReplicasDefaulted(info *resource.Info) bool {
// was unable to recover versioned info // was unable to recover versioned info
return false return false
} }
switch info.Mapping.APIVersion {
case "v1": switch info.Mapping.GroupVersionKind.GroupVersion() {
case unversioned.GroupVersion{Version: "v1"}:
if rc, ok := info.VersionedObject.(*v1.ReplicationController); ok { if rc, ok := info.VersionedObject.(*v1.ReplicationController); ok {
return rc.Spec.Replicas == nil return rc.Spec.Replicas == nil
} }

View File

@ -148,7 +148,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -165,11 +165,11 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
if describer, ok := kubectl.DescriberFor(group, mapping.Kind, client); ok { if describer, ok := kubectl.DescriberFor(group, mapping.GroupVersionKind.Kind, client); ok {
return describer, nil return describer, nil
} }
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind) return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
@ -242,18 +242,18 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
} }
}, },
Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return kubectl.ScalerFor(mapping.Kind, client) return kubectl.ScalerFor(mapping.GroupVersionKind.Kind, client)
}, },
Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return kubectl.ReaperFor(mapping.Kind, client) return kubectl.ReaperFor(mapping.GroupVersionKind.Kind, client)
}, },
Validator: func(validate bool, cacheDir string) (validation.Schema, error) { Validator: func(validate bool, cacheDir string) (validation.Schema, error) {
if validate { if validate {
@ -581,12 +581,12 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin
version := OutputVersion(cmd, defaultVersion) version := OutputVersion(cmd, defaultVersion)
if len(version) == 0 { if len(version) == 0 {
version = mapping.APIVersion version = mapping.GroupVersionKind.GroupVersion().String()
} }
if len(version) == 0 { if len(version) == 0 {
return nil, fmt.Errorf("you must specify an output-version when using this output format") return nil, fmt.Errorf("you must specify an output-version when using this output format")
} }
printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.APIVersion) printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.GroupVersionKind.GroupVersion().String())
} else { } else {
// Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper // Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper
columnLabel, err := cmd.Flags().GetStringSlice("label-columns") columnLabel, err := cmd.Flags().GetStringSlice("label-columns")

View File

@ -470,7 +470,7 @@ func TestResourceByNameWithoutRequireObject(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if mapping.Kind != "Pod" || mapping.Resource != "pods" { if mapping.GroupVersionKind.Kind != "Pod" || mapping.Resource != "pods" {
t.Errorf("unexpected resource mapping: %#v", mapping) t.Errorf("unexpected resource mapping: %#v", mapping)
} }
} }

View File

@ -254,7 +254,7 @@ func AsVersionedObjects(infos []*Info, version string) ([]runtime.Object, error)
} }
} }
converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.APIVersion) converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion().String())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -45,7 +45,7 @@ func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string,
// Visit implements Visitor // Visit implements Visitor
func (r *Selector) Visit(fn VisitorFunc) error { func (r *Selector) Visit(fn VisitorFunc) error {
list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().APIVersion, r.Selector) list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector)
if err != nil { if err != nil {
if errors.IsBadRequest(err) || errors.IsNotFound(err) { if errors.IsBadRequest(err) || errors.IsNotFound(err) {
if r.Selector.Empty() { if r.Selector.Empty() {
@ -70,7 +70,7 @@ func (r *Selector) Visit(fn VisitorFunc) error {
} }
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) { func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().APIVersion, r.Selector) return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector)
} }
// ResourceMapping returns the mapping for this resource and implements ResourceMapping // ResourceMapping returns the mapping for this resource and implements ResourceMapping