From 303bcad39815198c4df8d3ff8378d5cbe60ff72e Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 13 Nov 2015 08:13:55 -0500 Subject: [PATCH] use groupversion in RESTMapping --- pkg/api/install/install_test.go | 12 +- pkg/api/mapper.go | 21 +- pkg/api/meta/interfaces.go | 7 +- pkg/api/meta/restmapper.go | 93 +++++--- pkg/api/meta/restmapper_test.go | 209 +++++++++++------- pkg/api/unversioned/group_version.go | 23 +- .../componentconfig/install/install_test.go | 23 +- pkg/apis/extensions/install/install_test.go | 16 +- pkg/apiserver/apiserver_test.go | 160 +++++++------- pkg/apiserver/watch_test.go | 8 +- pkg/kubectl/cmd/autoscale.go | 6 +- pkg/kubectl/cmd/cmd_test.go | 20 +- pkg/kubectl/cmd/expose.go | 4 +- pkg/kubectl/cmd/get_test.go | 7 +- pkg/kubectl/cmd/rollingupdate.go | 6 +- pkg/kubectl/cmd/util/factory.go | 18 +- pkg/kubectl/resource/builder_test.go | 2 +- pkg/kubectl/resource/result.go | 2 +- pkg/kubectl/resource/selector.go | 4 +- 19 files changed, 384 insertions(+), 257 deletions(-) diff --git a/pkg/api/install/install_test.go b/pkg/api/install/install_test.go index 3bfc7f5897..ec9aadaa31 100644 --- a/pkg/api/install/install_test.go +++ b/pkg/api/install/install_test.go @@ -78,12 +78,16 @@ func TestRESTMapper(t *testing.T) { 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) } 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 { t.Errorf("unexpected error: %v", err) } @@ -91,11 +95,11 @@ func TestRESTMapper(t *testing.T) { if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" { t.Errorf("incorrect resource name: %#v", mapping) } - if mapping.APIVersion != version { + if mapping.GroupVersionKind.GroupVersion() != currGroupVersion { t.Errorf("incorrect version: %v", mapping) } - interfaces, _ := latest.GroupOrDie("").InterfacesFor(version) + interfaces, _ := latest.GroupOrDie("").InterfacesFor(currGroupVersion.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } diff --git a/pkg/api/mapper.go b/pkg/api/mapper.go index 10a38ff214..d657adf49f 100644 --- a/pkg/api/mapper.go +++ b/pkg/api/mapper.go @@ -17,9 +17,11 @@ limitations under the License. package api import ( + "fmt" "strings" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/util/sets" ) @@ -33,14 +35,23 @@ func RegisterRESTMapper(m meta.RESTMapper) { 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 { - 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 // our resources. - for _, version := range versions { - for kind, oType := range Scheme.KnownTypes(version) { + for _, gvString := range groupVersionStrings { + 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. // 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. @@ -51,7 +62,7 @@ func NewDefaultRESTMapper(group string, versions []string, interfacesFunc meta.V if rootScoped.Has(kind) { scope = meta.RESTScopeRoot } - mapper.Add(scope, kind, version, false) + mapper.Add(scope, kind, gv.String(), false) } } return mapper diff --git a/pkg/api/meta/interfaces.go b/pkg/api/meta/interfaces.go index 2ecc760701..0d12114d6b 100644 --- a/pkg/api/meta/interfaces.go +++ b/pkg/api/meta/interfaces.go @@ -17,6 +17,7 @@ limitations under the License. package meta import ( + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/types" ) @@ -124,10 +125,8 @@ type RESTScope interface { 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 - Kind string + + GroupVersionKind unversioned.GroupVersionKind // Scope contains the information needed to deal with REST Resources that are in a resource hierarchy Scope RESTScope diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 90a6c85e9a..27ec213665 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -20,6 +20,8 @@ package meta import ( "fmt" "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" ) // Implements RESTScope interface @@ -72,12 +74,12 @@ type typeMeta struct { // // TODO: Only accept plural for some operations for increased control? // (`get pod bar` vs `get pods bar`) +// TODO these maps should be keyed based on GroupVersionKinds type DefaultRESTMapper struct { mapping map[string]typeMeta reverse map[typeMeta]string scopes map[typeMeta]RESTScope - group string - versions []string + groupVersions []unversioned.GroupVersion plurals map[string]string singulars map[string]string 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 // 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(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) reverse := make(map[typeMeta]string) scopes := make(map[typeMeta]RESTScope) @@ -100,23 +107,29 @@ func NewDefaultRESTMapper(group string, versions []string, f VersionInterfacesFu singulars := make(map[string]string) // TODO: verify name mappings work correctly when versions differ + gvs := []unversioned.GroupVersion{} + for _, gvString := range gvStrings { + gvs = append(gvs, unversioned.ParseGroupVersionOrDie(gvString)) + } + return &DefaultRESTMapper{ mapping: mapping, reverse: reverse, scopes: scopes, - group: group, - versions: versions, + groupVersions: gvs, plurals: plurals, singulars: singulars, 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) m.plurals[singular] = plural m.singulars[plural] = singular - meta := typeMeta{APIVersion: version, Kind: kind} + meta := typeMeta{APIVersion: gv.String(), Kind: kind} _, ok1 := m.mapping[plural] _, ok2 := m.mapping[strings.ToLower(plural)] if !ok1 && !ok2 { @@ -177,74 +190,90 @@ func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultV } 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 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 // 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 // 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) { // Pick an appropriate version - var version string + var groupVersion *unversioned.GroupVersion hadVersion := false for _, v := range versions { if len(v) == 0 { continue } + currGroupVersion, err := unversioned.ParseGroupVersion(v) + if err != nil { + return nil, err + } + hadVersion = true - if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { - version = v + if _, ok := m.reverse[typeMeta{APIVersion: currGroupVersion.String(), Kind: kind}]; ok { + groupVersion = &currGroupVersion break } } // Use the default preferred versions - if !hadVersion && len(version) == 0 { - for _, v := range m.versions { - if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { - version = v + if !hadVersion && (groupVersion == nil) { + for _, currGroupVersion := range m.groupVersions { + if _, ok := m.reverse[typeMeta{APIVersion: currGroupVersion.String(), Kind: kind}]; ok { + groupVersion = &currGroupVersion break } } } - if len(version) == 0 { + if groupVersion == nil { 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 - resource, ok := m.reverse[typeMeta{APIVersion: version, Kind: kind}] + resource, ok := m.reverse[typeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}] if !ok { - found := []string{} - for _, v := range m.versions { - if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok { - found = append(found, v) + found := []unversioned.GroupVersion{} + for _, gv := range m.groupVersions { + if _, ok := m.reverse[typeMeta{APIVersion: gv.String(), Kind: kind}]; ok { + found = append(found, gv) } } 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 - scope, ok := m.scopes[typeMeta{APIVersion: version, Kind: kind}] + scope, ok := m.scopes[typeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}] 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 { - 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{ - Resource: resource, - APIVersion: version, - Kind: kind, - Scope: scope, + Resource: resource, + GroupVersionKind: gvk, + Scope: scope, Codec: interfaces.Codec, ObjectConvertor: interfaces.ObjectConvertor, diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 1f5e518d96..427392b68d 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -21,6 +21,7 @@ import ( "io" "testing" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" ) @@ -79,53 +80,71 @@ func unmatchedVersionInterfaces(version string) (*VersionInterfaces, error) { } func TestRESTMapperVersionAndKindForResource(t *testing.T) { + testGroup := "test.group" + testVersion := "test" + testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: testVersion} + testCases := []struct { - Resource string - Kind, APIVersion string - MixedCase bool - Err bool + Resource string + GroupVersionToRegister unversioned.GroupVersion + ExpectedGVK unversioned.GroupVersionKind + MixedCase bool + Err bool }{ - {Resource: "internalobjec", Err: true}, - {Resource: "internalObjec", Err: true}, + {Resource: "internalobjec", Err: true, GroupVersionToRegister: testGroupVersion}, + {Resource: "internalObjec", Err: true, GroupVersionToRegister: testGroupVersion}, - {Resource: "internalobject", Kind: "InternalObject", APIVersion: "test"}, - {Resource: "internalobjects", Kind: "InternalObject", APIVersion: "test"}, + {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, + {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, - {Resource: "internalobject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, - {Resource: "internalobjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, + {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, + {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, - {Resource: "internalObject", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, - {Resource: "internalObjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, + {Resource: "internalObject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, + {Resource: "internalObjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, } for i, testCase := range testCases { - mapper := NewDefaultRESTMapper("tgroup", []string{"test"}, fakeInterfaces) - mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) + mapper := NewDefaultRESTMapper(testGroup, []string{testGroupVersion.String()}, fakeInterfaces) + mapper.Add(RESTScopeNamespace, testCase.ExpectedGVK.Kind, testCase.GroupVersionToRegister.String(), testCase.MixedCase) v, k, err := mapper.VersionAndKindForResource(testCase.Resource) + hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: unexpected error behavior %t: %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) + if err != nil { + 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) } } } func TestRESTMapperGroupForResource(t *testing.T) { testCases := []struct { - Resource string - Kind, APIVersion, Group string - Err bool + Resource string + Kind string + GroupVersion unversioned.GroupVersion + Err bool }{ - {Resource: "myObject", Kind: "MyObject", APIVersion: "test", Group: "testapi"}, - {Resource: "myobject", Kind: "MyObject", APIVersion: "test", Group: "testapi2"}, - {Resource: "myObje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"}, - {Resource: "myobje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"}, + {Resource: "myObject", Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}}, + {Resource: "myobject", Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi2", Version: "test"}}, + {Resource: "myObje", Err: true, Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}}, + {Resource: "myobje", Err: true, Kind: "MyObject", GroupVersion: unversioned.GroupVersion{Group: "testapi", Version: "test"}}, } for i, testCase := range testCases { - mapper := NewDefaultRESTMapper(testCase.Group, []string{"test"}, fakeInterfaces) - mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, false) + mapper := NewDefaultRESTMapper(testCase.GroupVersion.Group, []string{testCase.GroupVersion.String()}, fakeInterfaces) + mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.GroupVersion.String(), false) g, err := mapper.GroupForResource(testCase.Resource) if testCase.Err { if err == nil { @@ -133,8 +152,8 @@ func TestRESTMapperGroupForResource(t *testing.T) { } } else if err != nil { t.Errorf("%d: unexpected error: %v", i, err) - } else if g != testCase.Group { - t.Errorf("%d: expected group %q, got %q", i, testCase.Group, g) + } else if g != testCase.GroupVersion.Group { + 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) { testCases := []struct { - Kind, APIVersion string - MixedCase bool - Plural string - Singular string + Kind string + MixedCase bool + Plural string + Singular string }{ - {Kind: "Pod", APIVersion: "test", MixedCase: true, Plural: "pods", Singular: "pod"}, - {Kind: "Pod", APIVersion: "test", MixedCase: false, Plural: "pods", Singular: "pod"}, + {Kind: "Pod", MixedCase: true, Plural: "pods", Singular: "pod"}, + {Kind: "Pod", MixedCase: false, Plural: "pods", Singular: "pod"}, - {Kind: "ReplicationController", APIVersion: "test", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"}, - {Kind: "ReplicationController", APIVersion: "test", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"}, + {Kind: "ReplicationController", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"}, + {Kind: "ReplicationController", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"}, - {Kind: "ImageRepository", APIVersion: "test", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"}, - {Kind: "ImageRepository", APIVersion: "test", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"}, + {Kind: "ImageRepository", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"}, + {Kind: "ImageRepository", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"}, - {Kind: "Status", APIVersion: "test", MixedCase: true, Plural: "statuses", Singular: "status"}, - {Kind: "Status", APIVersion: "test", MixedCase: false, Plural: "statuses", Singular: "status"}, + {Kind: "Status", MixedCase: true, 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 - {Kind: "lowercases", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, + {Kind: "lowercases", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, } 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 - mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) + mapper.Add(RESTScopeNamespace, testCase.Kind, testGroupVersion.String(), testCase.MixedCase) + singular, _ := mapper.ResourceSingularizer(testCase.Plural) if 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) { - testCases := []struct { - Kind string - APIVersions []string - MixedCase bool - DefaultVersions []string + testGroup := "tgroup" + testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: "test"} - Resource string - Version string - Err bool + testCases := []struct { + Kind string + APIGroupVersions []unversioned.GroupVersion + MixedCase bool + DefaultVersions []string + + Resource string + ExpectedGroupVersion *unversioned.GroupVersion + Err bool }{ {Kind: "Unknown", 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{"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{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{"test"}, Kind: "InternalObject", APIVersions: []string{"test"}, MixedCase: true, Resource: "internalObjects"}, + {DefaultVersions: []string{testGroupVersion.String()}, Kind: "InternalObject", APIGroupVersions: []unversioned.GroupVersion{{Group: testGroup, Version: "test"}}, 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 } for i, testCase := range testCases { - mapper := NewDefaultRESTMapper("tgroup", testCase.DefaultVersions, fakeInterfaces) - mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase) - mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...) + internalGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: "test"} + + 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 if hasErr != testCase.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 { 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 { 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) { - mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, fakeInterfaces) - mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) - mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false) + expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"} + expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"} + 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 mapping, err := mapper.RESTMapping("OtherObject") if err != nil { 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) } @@ -275,45 +315,48 @@ func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { if err != nil { 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) } // mismatch of version - mapping, err = mapper.RESTMapping("InternalObject", "test2") + mapping, err = mapper.RESTMapping("InternalObject", expectedGroupVersion2.String()) if err == nil { t.Errorf("unexpected non-error") } - mapping, err = mapper.RESTMapping("OtherObject", "test1") + mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion1.String()) if err == nil { t.Errorf("unexpected non-error") } // not in the search versions - mapping, err = mapper.RESTMapping("OtherObject", "test3") + mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String()) if err == nil { t.Errorf("unexpected non-error") } // explicit search order - mapping, err = mapper.RESTMapping("OtherObject", "test3", "test1") + mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion1.String()) if err == nil { t.Errorf("unexpected non-error") } - mapping, err = mapper.RESTMapping("OtherObject", "test3", "test2") + mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion2.String()) 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) } } func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { - mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, unmatchedVersionInterfaces) - mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) - _, err := mapper.RESTMapping("InternalObject", "test1") + expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"} + expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"} + + 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 { t.Errorf("unexpected non-error") } diff --git a/pkg/api/unversioned/group_version.go b/pkg/api/unversioned/group_version.go index 8d294582ff..1b92515662 100644 --- a/pkg/api/unversioned/group_version.go +++ b/pkg/api/unversioned/group_version.go @@ -22,7 +22,26 @@ import ( "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. type GroupVersion struct { Group string @@ -31,7 +50,7 @@ type GroupVersion struct { // String puts "group" and "version" into a single "group/version" string. For the legacy v1 // it returns "v1". -func (gv *GroupVersion) String() string { +func (gv GroupVersion) String() string { // special case of "v1" for backward compatibility if gv.Group == "" && gv.Version == "v1" { return gv.Version diff --git a/pkg/apis/componentconfig/install/install_test.go b/pkg/apis/componentconfig/install/install_test.go index fbd3df1f4b..0d68d45a3c 100644 --- a/pkg/apis/componentconfig/install/install_test.go +++ b/pkg/apis/componentconfig/install/install_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api/latest" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/componentconfig" ) @@ -53,28 +54,36 @@ func TestInterfacesFor(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" { - t.Errorf("unexpected version mapping: %s %s %v", v, k, err) + expectedGroupVersion := unversioned.GroupVersion{Group: "componentconfig", Version: "v1alpha1"} + + 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) } - for _, groupVersion := range latest.GroupOrDie("componentconfig").GroupVersions { - mapping, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping("KubeProxyConfiguration", groupVersion) + for _, groupVersionString := range latest.GroupOrDie("componentconfig").GroupVersions { + gv, err := unversioned.ParseGroupVersion(groupVersionString) if err != nil { 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" { t.Errorf("incorrect resource name: %#v", mapping) } - if mapping.APIVersion != groupVersion { + if mapping.GroupVersionKind.GroupVersion() != gv { t.Errorf("incorrect groupVersion: %v", mapping) } - interfaces, _ := latest.GroupOrDie("componentconfig").InterfacesFor(groupVersion) + interfaces, _ := latest.GroupOrDie("componentconfig").InterfacesFor(gv.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } diff --git a/pkg/apis/extensions/install/install_test.go b/pkg/apis/extensions/install/install_test.go index 97c28a94a2..f9062963ed 100644 --- a/pkg/apis/extensions/install/install_test.go +++ b/pkg/apis/extensions/install/install_test.go @@ -75,16 +75,20 @@ func TestInterfacesFor(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) } - 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) } - for _, groupVersion := range latest.GroupOrDie("extensions").GroupVersions { - mapping, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("HorizontalPodAutoscaler", groupVersion) + for _, groupVersionString := range latest.GroupOrDie("extensions").GroupVersions { + gv, err := unversioned.ParseGroupVersion(groupVersionString) + + mapping, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping("HorizontalPodAutoscaler", gv.String()) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -92,11 +96,11 @@ func TestRESTMapper(t *testing.T) { if mapping.Resource != "horizontalpodautoscalers" { t.Errorf("incorrect resource name: %#v", mapping) } - if mapping.APIVersion != groupVersion { + if mapping.GroupVersionKind.GroupVersion() != gv { t.Errorf("incorrect groupVersion: %v", mapping) } - interfaces, _ := latest.GroupOrDie("extensions").InterfacesFor(groupVersion) + interfaces, _ := latest.GroupOrDie("extensions").InterfacesFor(gv.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 5fca34453a..90b1c4a318 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -59,19 +59,16 @@ func convert(obj runtime.Object) (runtime.Object, error) { // This creates fake API versions, similar to api/latest.go. var testAPIGroup = "test.group" var testGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version"} -var testVersion = testGroupVersion.String() var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version2"} -var newVersion = newGroupVersion.String() var prefix = "apis" var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"} -var grouplessVersion = grouplessGroupVersion.String() 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 codec = runtime.CodecFor(api.Scheme, testVersion) -var newCodec = runtime.CodecFor(api.Scheme, newVersion) +var groupVersions = []unversioned.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion} +var codec = runtime.CodecFor(api.Scheme, testGroupVersion.String()) +var newCodec = runtime.CodecFor(api.Scheme, newGroupVersion.String()) var accessor = meta.NewAccessor() var versioner runtime.ResourceVersioner = accessor @@ -81,32 +78,37 @@ var admissionControl admission.Interface var requestContextMapper api.RequestContextMapper func interfacesFor(version string) (*meta.VersionInterfaces, error) { - switch version { - case testVersion: + gv, err := unversioned.ParseGroupVersion(version) + if err != nil { + return nil, err + } + switch gv { + case testGroupVersion: return &meta.VersionInterfaces{ Codec: codec, ObjectConvertor: api.Scheme, MetadataAccessor: accessor, }, nil - case newVersion: + case newGroupVersion: return &meta.VersionInterfaces{ Codec: newCodec, ObjectConvertor: api.Scheme, MetadataAccessor: accessor, }, nil - case grouplessVersion: + case grouplessGroupVersion: return &meta.VersionInterfaces{ Codec: grouplessCodec, ObjectConvertor: api.Scheme, MetadataAccessor: accessor, }, nil 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 { - return meta.NewDefaultRESTMapper("testgroup", versions, interfacesFor) + gvStrings := []string{testGroupVersion.String(), newGroupVersion.String()} + return meta.NewDefaultRESTMapper(testAPIGroup, gvStrings, interfacesFor) } func addGrouplessTypes() { @@ -120,9 +122,9 @@ func addGrouplessTypes() { TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` } 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{}) - api.Scheme.AddKnownTypes(grouplessVersion, &api.Pod{}) + api.Scheme.AddKnownTypes(grouplessGroupVersion.String(), &api.Pod{}) } func addTestTypes() { @@ -136,9 +138,9 @@ func addTestTypes() { TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` } 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{}) - api.Scheme.AddKnownTypes(testVersion, &api.Pod{}) + api.Scheme.AddKnownTypes(testGroupVersion.String(), &api.Pod{}) } func addNewTestTypes() { @@ -152,7 +154,7 @@ func addNewTestTypes() { TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` } 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{}) } @@ -172,13 +174,13 @@ func init() { // enumerate all supported versions, get the kinds, and register with // the mapper how to address our resources - for _, version := range versions { - for kind := range api.Scheme.KnownTypes(version) { + for _, gv := range groupVersions { + for kind := range api.Scheme.KnownTypes(gv.String()) { root := bool(kind == "SimpleRoot") if root { - nsMapper.Add(meta.RESTScopeRoot, kind, version, false) + nsMapper.Add(meta.RESTScopeRoot, kind, gv.String(), false) } 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() requestContextMapper = api.NewRequestContextMapper() - api.Scheme.AddFieldLabelConversionFunc(grouplessVersion, "Simple", + api.Scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.String(), "Simple", func(label, value string) (string, string, error) { return label, value, nil }, ) - api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple", + api.Scheme.AddFieldLabelConversionFunc(testGroupVersion.String(), "Simple", func(label, value string) (string, string, error) { return label, value, nil }, ) - api.Scheme.AddFieldLabelConversionFunc(newVersion, "Simple", + api.Scheme.AddFieldLabelConversionFunc(newGroupVersion.String(), "Simple", func(label, value string) (string, string, error) { return label, value, nil }, @@ -653,31 +655,31 @@ func TestNotFound(t *testing.T) { } cases := map[string]T{ // Positive checks to make sure everything is wired correctly - "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusOK}, - "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusOK}, + "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK}, + "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, - "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, - "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, - "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, - "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, - "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, - "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots", http.StatusMethodNotAllowed}, - "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/simpleroots/bar/baz", http.StatusNotFound}, - "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, + "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", 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 + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", 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 missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/blah", http.StatusNotFound}, - "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, - "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, - "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, - "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, - "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, - "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, - "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/", http.StatusNotFound}, - "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", 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 + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", 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 + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound}, + "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 "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, @@ -753,15 +755,15 @@ func TestUnimplementedRESTStorage(t *testing.T) { ErrCode int } cases := map[string]T{ - "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, - "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, - "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo", http.StatusNotFound}, - "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, - "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessVersion + "/foo/bar", http.StatusNotFound}, - "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo", http.StatusNotFound}, - "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/watch/foo/bar", http.StatusNotFound}, - "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/proxy/foo/bar", http.StatusNotFound}, - "groupless redirect object": {"GET", "/" + grouplessPrefix + "/" + grouplessVersion + "/redirect/foo/bar", http.StatusNotFound}, + "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, + "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, + "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound}, + "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, + "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/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 list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, @@ -841,76 +843,76 @@ func TestList(t *testing.T) { // legacy namespace param is ignored { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=", namespace: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", legacy: true, }, { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple?namespace=other", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other", namespace: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", 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: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", legacy: true, label: "a=b", field: "c=d", }, // legacy api version is honored { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", namespace: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", legacy: true, }, { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", namespace: "other", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", 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", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", legacy: true, label: "a=b", field: "c=d", }, // list items across all namespaces { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", namespace: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", legacy: true, }, // list items in a namespace in the path { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/default/simple", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", 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", - 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", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/namespaces/other/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", label: "a=b", field: "c=d", }, // list items across all namespaces { - url: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", namespace: "", - selfLink: "/" + grouplessPrefix + "/" + grouplessVersion + "/simple", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", }, // Group API @@ -2862,7 +2864,7 @@ func TestCreateChecksAPIVersion(t *testing.T) { if err != nil { 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 { t.Errorf("unexpected error: %v", err) } @@ -2903,7 +2905,7 @@ func TestCreateDefaultsAPIVersion(t *testing.T) { 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 { t.Errorf("unexpected error: %v", err) } @@ -2927,7 +2929,7 @@ func TestUpdateChecksAPIVersion(t *testing.T) { if err != nil { 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 { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/apiserver/watch_test.go b/pkg/apiserver/watch_test.go index b82c031c39..9575ff00fa 100644 --- a/pkg/apiserver/watch_test.go +++ b/pkg/apiserver/watch_test.go @@ -178,8 +178,8 @@ func TestWatchParamParsing(t *testing.T) { dest, _ := url.Parse(server.URL) - rootPath := "/" + prefix + "/" + testVersion + "/watch/simples" - namespacedPath := "/" + prefix + "/" + testVersion + "/watch/namespaces/other/simpleroots" + rootPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + namespacedPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/other/simpleroots" table := []struct { path string @@ -358,13 +358,13 @@ func TestWatchHTTPTimeout(t *testing.T) { // Setup a client dest, _ := url.Parse(s.URL) - dest.Path = "/" + prefix + "/" + newVersion + "/simple" + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple" dest.RawQuery = "watch=true" req, _ := http.NewRequest("GET", dest.String(), nil) client := http.Client{} 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 decoder := json.NewDecoder(resp.Body) diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index 92cce454c9..c029bf4bcb 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -96,7 +96,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } info := infos[0] mapping := info.ResourceMapping() - if err := f.CanBeAutoscaled(mapping.Kind); err != nil { + if err := f.CanBeAutoscaled(mapping.GroupVersionKind.Kind); err != nil { return err } @@ -111,9 +111,9 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] name := info.Name params["default-name"] = name - params["scaleRef-kind"] = mapping.Kind + params["scaleRef-kind"] = mapping.GroupVersionKind.Kind params["scaleRef-name"] = name - params["scaleRef-apiVersion"] = mapping.APIVersion + params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String() if err = kubectl.ValidateParams(names, params); err != nil { return err diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index dc69efd551..3d6656d014 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -76,23 +76,27 @@ func versionErrIfFalse(b bool) error { 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) { scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName("", "Type", &internalType{}) - scheme.AddKnownTypeWithName("unlikelyversion", "Type", &externalType{}) + scheme.AddKnownTypeWithName(internalGV.Version, "Type", &internalType{}) + 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. - scheme.AddKnownTypeWithName(testapi.Default.Version(), "Type", &ExternalType2{}) + scheme.AddKnownTypeWithName(validVersionGV.String(), "Type", &ExternalType2{}) - codec := runtime.CodecFor(scheme, "unlikelyversion") - validVersion := testapi.Default.Version() - mapper := meta.NewDefaultRESTMapper("apitest", []string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, error) { + codec := runtime.CodecFor(scheme, unlikelyGV.String()) + mapper := meta.NewDefaultRESTMapper("apitest", []string{unlikelyGV.String(), validVersionGV.String()}, func(version string) (*meta.VersionInterfaces, error) { return &meta.VersionInterfaces{ Codec: runtime.CodecFor(scheme, version), ObjectConvertor: scheme, 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) { mixedCase := false scope := meta.RESTScopeNamespace diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index 6bbc6f23de..606b20707e 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -120,7 +120,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str } info := infos[0] mapping := info.ResourceMapping() - if err := f.CanBeExposed(mapping.Kind); err != nil { + if err := f.CanBeExposed(mapping.GroupVersionKind.Kind); err != nil { return err } // 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 { - object, err = cmdutil.Merge(object, inline, mapping.Kind) + object, err = cmdutil.Merge(object, inline, mapping.GroupVersionKind.Kind) if err != nil { return err } diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index d33c409a77..d2c659b389 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -159,19 +159,19 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) { "handles specific version": { outputVersion: testapi.Default.Version(), listVersion: testapi.Default.Version(), - testtypeVersion: "unlikelyversion", + testtypeVersion: unlikelyGV.String(), rcVersion: testapi.Default.Version(), }, "handles second specific version": { outputVersion: "unlikelyversion", listVersion: testapi.Default.Version(), - testtypeVersion: "unlikelyversion", + testtypeVersion: unlikelyGV.String(), rcVersion: testapi.Default.Version(), // see expected behavior 3b }, "handles common version": { outputVersion: testapi.Default.Version(), listVersion: testapi.Default.Version(), - testtypeVersion: "unlikelyversion", + testtypeVersion: unlikelyGV.String(), rcVersion: testapi.Default.Version(), }, } @@ -198,6 +198,7 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) { cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("output", "json") + cmd.Flags().Set("output-version", test.outputVersion) err := RunGet(f, buf, cmd, []string{"type/foo", "replicationcontrollers/foo"}, &GetOptions{}) if err != nil { diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index d41b4887aa..5ce1266f33 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -382,8 +383,9 @@ func isReplicasDefaulted(info *resource.Info) bool { // was unable to recover versioned info 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 { return rc.Spec.Replicas == nil } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index ea98c0f341..81711c48a6 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -148,7 +148,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { if err != nil { return nil, err } - client, err := clients.ClientForVersion(mapping.APIVersion) + client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String()) if err != nil { return nil, err } @@ -165,11 +165,11 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { if err != nil { return nil, err } - client, err := clients.ClientForVersion(mapping.APIVersion) + client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String()) if err != nil { 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 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) { - client, err := clients.ClientForVersion(mapping.APIVersion) + client, err := clients.ClientForVersion(mapping.GroupVersionKind.GroupVersion().String()) if err != nil { return nil, err } - return kubectl.ScalerFor(mapping.Kind, client) + return kubectl.ScalerFor(mapping.GroupVersionKind.Kind, client) }, 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 { 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) { if validate { @@ -581,12 +581,12 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin version := OutputVersion(cmd, defaultVersion) if len(version) == 0 { - version = mapping.APIVersion + version = mapping.GroupVersionKind.GroupVersion().String() } if len(version) == 0 { 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 { // Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper columnLabel, err := cmd.Flags().GetStringSlice("label-columns") diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index dafdd1d153..c98592df66 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -470,7 +470,7 @@ func TestResourceByNameWithoutRequireObject(t *testing.T) { if err != nil { 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) } } diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index aaf7fd07e0..07ee99b6a2 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -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 { return nil, err } diff --git a/pkg/kubectl/resource/selector.go b/pkg/kubectl/resource/selector.go index 75e417f02d..4bca8d9fd5 100644 --- a/pkg/kubectl/resource/selector.go +++ b/pkg/kubectl/resource/selector.go @@ -45,7 +45,7 @@ func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, // Visit implements Visitor 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 errors.IsBadRequest(err) || errors.IsNotFound(err) { if r.Selector.Empty() { @@ -70,7 +70,7 @@ func (r *Selector) Visit(fn VisitorFunc) 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