diff --git a/pkg/api/install/install_test.go b/pkg/api/install/install_test.go index d65071e0c6b..cc634366b19 100644 --- a/pkg/api/install/install_test.go +++ b/pkg/api/install/install_test.go @@ -78,7 +78,7 @@ func TestRESTMapper(t *testing.T) { rcGVK := gv.WithKind("ReplicationController") podTemplateGVK := gv.WithKind("PodTemplate") - if gvk, err := latest.GroupOrDie(internal.GroupName).RESTMapper.KindFor("replicationcontrollers"); err != nil || gvk != rcGVK { + if gvk, err := latest.GroupOrDie(internal.GroupName).RESTMapper.KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } diff --git a/pkg/api/meta/interfaces.go b/pkg/api/meta/interfaces.go index c8b8d0ce394..8f14a3fab50 100644 --- a/pkg/api/meta/interfaces.go +++ b/pkg/api/meta/interfaces.go @@ -158,12 +158,23 @@ type RESTMapping struct { // TODO(caesarxuchao): Add proper multi-group support so that kinds & resources are // scoped to groups. See http://issues.k8s.io/12413 and http://issues.k8s.io/10009. type RESTMapper interface { - // KindFor takes a resource and returns back the unambiguous Kind (GroupVersionKind) - KindFor(resource string) (unversioned.GroupVersionKind, error) + // KindFor takes a partial resource and returns back the single match. Returns an error if there are multiple matches + KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) + + // KindsFor takes a partial resource and returns back the list of potential kinds in priority order + KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) + + // ResourceFor takes a partial resource and returns back the single match. Returns an error if there are multiple matches + ResourceFor(input unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) + + // ResourcesFor takes a partial resource and returns back the list of potential resource in priority order + ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error) AliasesForResource(resource string) ([]string, bool) ResourceSingularizer(resource string) (singular string, err error) - ResourceIsValid(resource string) bool + + // ResourceIsValid takes a partial resource and returns back whether or not the resource matches at least one kind + ResourceIsValid(resource unversioned.GroupVersionResource) bool } diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index ed3e38cc7c1..449f519afca 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -19,9 +19,11 @@ package meta import ( "fmt" + "sort" "strings" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util/sets" ) // Implements RESTScope interface @@ -71,11 +73,11 @@ var RESTScopeRoot = &restScope{ type DefaultRESTMapper struct { defaultGroupVersions []unversioned.GroupVersion - resourceToKind map[string]unversioned.GroupVersionKind - kindToPluralResource map[unversioned.GroupVersionKind]string + resourceToKind map[unversioned.GroupVersionResource]unversioned.GroupVersionKind + kindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResource kindToScope map[unversioned.GroupVersionKind]RESTScope - singularToPlural map[string]string - pluralToSingular map[string]string + singularToPlural map[unversioned.GroupVersionResource]unversioned.GroupVersionResource + pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource interfacesFunc VersionInterfacesFunc } @@ -92,11 +94,11 @@ type VersionInterfacesFunc func(version unversioned.GroupVersion) (*VersionInter // 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(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper { - resourceToKind := make(map[string]unversioned.GroupVersionKind) - kindToPluralResource := make(map[unversioned.GroupVersionKind]string) + resourceToKind := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionKind) + kindToPluralResource := make(map[unversioned.GroupVersionKind]unversioned.GroupVersionResource) kindToScope := make(map[unversioned.GroupVersionKind]RESTScope) - singularToPlural := make(map[string]string) - pluralToSingular := make(map[string]string) + singularToPlural := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) + pluralToSingular := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) // TODO: verify name mappings work correctly when versions differ return &DefaultRESTMapper{ @@ -110,45 +112,54 @@ func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f Ver } } -func (m *DefaultRESTMapper) Add(gvk unversioned.GroupVersionKind, scope RESTScope, mixedCase bool) { - plural, singular := KindToResource(gvk.Kind, mixedCase) +func (m *DefaultRESTMapper) Add(kind unversioned.GroupVersionKind, scope RESTScope, mixedCase bool) { + plural, singular := KindToResource(kind, mixedCase) + lowerPlural := plural.GroupVersion().WithResource(strings.ToLower(plural.Resource)) + lowerSingular := singular.GroupVersion().WithResource(strings.ToLower(singular.Resource)) + m.singularToPlural[singular] = plural m.pluralToSingular[plural] = singular - _, ok1 := m.resourceToKind[plural] - _, ok2 := m.resourceToKind[strings.ToLower(plural)] - if !ok1 && !ok2 { - m.resourceToKind[plural] = gvk - m.resourceToKind[singular] = gvk - if strings.ToLower(plural) != plural { - m.resourceToKind[strings.ToLower(plural)] = gvk - m.resourceToKind[strings.ToLower(singular)] = gvk - } + m.singularToPlural[lowerSingular] = lowerPlural + m.pluralToSingular[lowerPlural] = lowerSingular + + if _, mixedCaseExists := m.resourceToKind[plural]; !mixedCaseExists { + m.resourceToKind[plural] = kind + m.resourceToKind[singular] = kind } - m.kindToPluralResource[gvk] = plural - m.kindToScope[gvk] = scope + + if _, lowerCaseExists := m.resourceToKind[lowerPlural]; !lowerCaseExists && (lowerPlural != plural) { + m.resourceToKind[lowerPlural] = kind + m.resourceToKind[lowerSingular] = kind + } + + m.kindToPluralResource[kind] = plural + m.kindToScope[kind] = scope } // KindToResource converts Kind to a resource name. -func KindToResource(kind string, mixedCase bool) (plural, singular string) { - if len(kind) == 0 { +func KindToResource(kind unversioned.GroupVersionKind, mixedCase bool) (plural, singular unversioned.GroupVersionResource) { + kindName := kind.Kind + if len(kindName) == 0 { return } if mixedCase { // Legacy support for mixed case names - singular = strings.ToLower(kind[:1]) + kind[1:] + singular = kind.GroupVersion().WithResource(strings.ToLower(kindName[:1]) + kindName[1:]) } else { - singular = strings.ToLower(kind) + singular = kind.GroupVersion().WithResource(strings.ToLower(kindName)) } - if strings.HasSuffix(singular, "endpoints") { + + singularName := singular.Resource + if strings.HasSuffix(singularName, "endpoints") { plural = singular } else { - switch string(singular[len(singular)-1]) { + switch string(singularName[len(singularName)-1]) { case "s": - plural = singular + "es" + plural = kind.GroupVersion().WithResource(singularName + "es") case "y": - plural = strings.TrimSuffix(singular, "y") + "ies" + plural = kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies") default: - plural = singular + "s" + plural = kind.GroupVersion().WithResource(singularName + "s") } } return @@ -156,21 +167,240 @@ func KindToResource(kind string, mixedCase bool) (plural, singular string) { // ResourceSingularizer implements RESTMapper // It converts a resource name from plural to singular (e.g., from pods to pod) -func (m *DefaultRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { +// It must have exactly one match and it must match case perfectly. This is congruent with old functionality +func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) { + partialResource := unversioned.GroupVersionResource{Resource: resourceType} + resource, err := m.ResourceFor(partialResource) + if err != nil { + return resourceType, err + } + singular, ok := m.pluralToSingular[resource] if !ok { - return resource, fmt.Errorf("no singular of resource %q has been defined", resource) + return resourceType, fmt.Errorf("no singular of resource %v has been defined", resource) } - return singular, nil + return singular.Resource, nil } -// VersionAndKindForResource implements RESTMapper -func (m *DefaultRESTMapper) KindFor(resource string) (unversioned.GroupVersionKind, error) { - gvk, ok := m.resourceToKind[strings.ToLower(resource)] - if !ok { - return gvk, fmt.Errorf("in version and kind for resource, no resource %q has been defined", resource) +func (m *DefaultRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + hasResource := len(resource.Resource) > 0 + hasGroup := len(resource.Group) > 0 + hasVersion := len(resource.Version) > 0 + + if !hasResource { + return nil, fmt.Errorf("a resource must be present, got: %v", resource) } - return gvk, nil + + ret := []unversioned.GroupVersionResource{} + switch { + // fully qualified. Find the exact match + case hasGroup && hasVersion: + for plural, singular := range m.pluralToSingular { + if singular == resource { + ret = append(ret, plural) + break + } + if plural == resource { + ret = append(ret, plural) + break + } + } + + case hasGroup: + requestedGroupResource := resource.GroupResource() + for currResource := range m.pluralToSingular { + if currResource.GroupResource() == requestedGroupResource { + ret = append(ret, currResource) + } + } + + case hasVersion: + for currResource := range m.pluralToSingular { + if currResource.Version == resource.Version && currResource.Resource == resource.Resource { + ret = append(ret, currResource) + } + } + + default: + for currResource := range m.pluralToSingular { + if currResource.Resource == resource.Resource { + ret = append(ret, currResource) + } + } + } + + if len(ret) == 0 { + return nil, fmt.Errorf("no resource %v has been defined; known resources: %v", resource, m.pluralToSingular) + } + + sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) + return ret, nil +} + +func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + resources, err := m.ResourcesFor(resource) + if err != nil { + return unversioned.GroupVersionResource{}, err + } + if len(resources) == 1 { + return resources[0], nil + } + + return unversioned.GroupVersionResource{}, fmt.Errorf("%v is ambiguous, got: %v", resource, resources) +} + +func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { + resource := input.GroupVersion().WithResource(strings.ToLower(input.Resource)) + + hasResource := len(resource.Resource) > 0 + hasGroup := len(resource.Group) > 0 + hasVersion := len(resource.Version) > 0 + + if !hasResource { + return nil, fmt.Errorf("a resource must be present, got: %v", resource) + } + + ret := []unversioned.GroupVersionKind{} + switch { + // fully qualified. Find the exact match + case hasGroup && hasVersion: + kind, exists := m.resourceToKind[resource] + if exists { + ret = append(ret, kind) + } + + case hasGroup: + requestedGroupResource := resource.GroupResource() + for currResource, currKind := range m.resourceToKind { + if currResource.GroupResource() == requestedGroupResource { + ret = append(ret, currKind) + } + } + + case hasVersion: + for currResource, currKind := range m.resourceToKind { + if currResource.Version == resource.Version && currResource.Resource == resource.Resource { + ret = append(ret, currKind) + } + } + + default: + for currResource, currKind := range m.resourceToKind { + if currResource.Resource == resource.Resource { + ret = append(ret, currKind) + } + } + } + + if len(ret) == 0 { + return nil, fmt.Errorf("no kind %v has been defined; known resources: %v", resource, m.pluralToSingular) + } + + sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) + return ret, nil +} + +func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + kinds, err := m.KindsFor(resource) + if err != nil { + return unversioned.GroupVersionKind{}, err + } + + // TODO for each group, choose the most preferred (first) version. This keeps us consistent with code today. + // eventually, we'll need a RESTMapper that is aware of what's available server-side and deconflicts that with + // user preferences + oneKindPerGroup := []unversioned.GroupVersionKind{} + groupsAdded := sets.String{} + for _, kind := range kinds { + if groupsAdded.Has(kind.Group) { + continue + } + + oneKindPerGroup = append(oneKindPerGroup, kind) + groupsAdded.Insert(kind.Group) + } + + if len(oneKindPerGroup) == 1 { + return oneKindPerGroup[0], nil + } + + return unversioned.GroupVersionKind{}, fmt.Errorf("%v is ambiguous, got: %v", resource, kinds) +} + +type kindByPreferredGroupVersion struct { + list []unversioned.GroupVersionKind + sortOrder []unversioned.GroupVersion +} + +func (o kindByPreferredGroupVersion) Len() int { return len(o.list) } +func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } +func (o kindByPreferredGroupVersion) Less(i, j int) bool { + lhs := o.list[i] + rhs := o.list[j] + if lhs == rhs { + return false + } + + if lhs.GroupVersion() == rhs.GroupVersion() { + return lhs.Kind < rhs.Kind + } + + // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order + lhsIndex := -1 + rhsIndex := -1 + + for i := range o.sortOrder { + if o.sortOrder[i] == lhs.GroupVersion() { + lhsIndex = i + } + if o.sortOrder[i] == rhs.GroupVersion() { + rhsIndex = i + } + } + + if rhsIndex == -1 { + return true + } + + return lhsIndex < rhsIndex +} + +type resourceByPreferredGroupVersion struct { + list []unversioned.GroupVersionResource + sortOrder []unversioned.GroupVersion +} + +func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) } +func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } +func (o resourceByPreferredGroupVersion) Less(i, j int) bool { + lhs := o.list[i] + rhs := o.list[j] + if lhs == rhs { + return false + } + + if lhs.GroupVersion() == rhs.GroupVersion() { + return lhs.Resource < rhs.Resource + } + + // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order + lhsIndex := -1 + rhsIndex := -1 + + for i := range o.sortOrder { + if o.sortOrder[i] == lhs.GroupVersion() { + lhsIndex = i + } + if o.sortOrder[i] == rhs.GroupVersion() { + rhsIndex = i + } + } + + if rhsIndex == -1 { + return true + } + + return lhsIndex < rhsIndex } // RESTMapping returns a struct representing the resource path and conversion interfaces a @@ -238,7 +468,7 @@ func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...st } retVal := &RESTMapping{ - Resource: resource, + Resource: resource.Resource, GroupVersionKind: *gvk, Scope: scope, @@ -269,8 +499,8 @@ func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) { return nil, false } -// ResourceIsValid takes a string (kind) and checks if it's a valid resource -func (m *DefaultRESTMapper) ResourceIsValid(resource string) bool { +// ResourceIsValid takes a partial resource and checks if it's valid +func (m *DefaultRESTMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { _, err := m.KindFor(resource) return err == nil } @@ -290,10 +520,41 @@ func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, return } -// VersionAndKindForResource provides the Version and Kind mappings for the -// REST resources. This implementation supports multiple REST schemas and return +func (m MultiRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionResource, err error) { + for _, t := range m { + gvk, err = t.ResourcesFor(resource) + if err == nil { + return + } + } + return +} + +// KindsFor provides the Kind mappings for the REST resources. This implementation supports multiple REST schemas and returns // the first match. -func (m MultiRESTMapper) KindFor(resource string) (gvk unversioned.GroupVersionKind, err error) { +func (m MultiRESTMapper) KindsFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { + for _, t := range m { + gvk, err = t.KindsFor(resource) + if err == nil { + return + } + } + return +} + +func (m MultiRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (gvk unversioned.GroupVersionResource, err error) { + for _, t := range m { + gvk, err = t.ResourceFor(resource) + if err == nil { + return + } + } + return +} + +// KindsFor provides the Kind mapping for the REST resources. This implementation supports multiple REST schemas and returns +// the first match. +func (m MultiRESTMapper) KindFor(resource unversioned.GroupVersionResource) (gvk unversioned.GroupVersionKind, err error) { for _, t := range m { gvk, err = t.KindFor(resource) if err == nil { @@ -327,7 +588,7 @@ func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok } // ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource -func (m MultiRESTMapper) ResourceIsValid(resource string) bool { +func (m MultiRESTMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { for _, t := range m { if t.ResourceIsValid(resource) { return true diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 134705a6514..603848d5c87 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -20,6 +20,8 @@ import ( "errors" "io" "net/url" + "reflect" + "strings" "testing" "k8s.io/kubernetes/pkg/api/unversioned" @@ -92,23 +94,23 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) { testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: testVersion} testCases := []struct { - Resource string + Resource unversioned.GroupVersionResource GroupVersionToRegister unversioned.GroupVersion ExpectedGVK unversioned.GroupVersionKind MixedCase bool Err bool }{ - {Resource: "internalobjec", Err: true}, - {Resource: "internalObjec", Err: true}, + {Resource: unversioned.GroupVersionResource{Resource: "internalobjec"}, Err: true}, + {Resource: unversioned.GroupVersionResource{Resource: "internalObjec"}, Err: true}, - {Resource: "internalobject", ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, - {Resource: "internalobjects", ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalobject"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalobjects"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, - {Resource: "internalobject", MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, - {Resource: "internalobjects", MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalobject"}, MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalobjects"}, MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, - {Resource: "internalObject", MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, - {Resource: "internalObjects", MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalObject"}, MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: unversioned.GroupVersionResource{Resource: "internalObjects"}, MixedCase: true, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]unversioned.GroupVersion{testGroupVersion}, fakeInterfaces) @@ -134,18 +136,19 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) { func TestRESTMapperGroupForResource(t *testing.T) { testCases := []struct { - Resource string + Resource unversioned.GroupVersionResource GroupVersionKind unversioned.GroupVersionKind Err bool }{ - {Resource: "myObject", GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, - {Resource: "myobject", GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi2", Version: "test", Kind: "MyObject"}}, - {Resource: "myObje", Err: true, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, - {Resource: "myobje", Err: true, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + {Resource: unversioned.GroupVersionResource{Resource: "myObject"}, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + {Resource: unversioned.GroupVersionResource{Resource: "myobject"}, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi2", Version: "test", Kind: "MyObject"}}, + {Resource: unversioned.GroupVersionResource{Resource: "myObje"}, Err: true, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + {Resource: unversioned.GroupVersionResource{Resource: "myobje"}, Err: true, GroupVersionKind: unversioned.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]unversioned.GroupVersion{testCase.GroupVersionKind.GroupVersion()}, fakeInterfaces) mapper.Add(testCase.GroupVersionKind, RESTScopeNamespace, false) + actualGVK, err := mapper.KindFor(testCase.Resource) if testCase.Err { if err == nil { @@ -159,6 +162,230 @@ func TestRESTMapperGroupForResource(t *testing.T) { } } +func TestRESTMapperKindsFor(t *testing.T) { + testCases := []struct { + Name string + PreferredOrder []unversioned.GroupVersion + KindsToRegister []unversioned.GroupVersionKind + PartialResourceToRequest unversioned.GroupVersionResource + + ExpectedKinds []unversioned.GroupVersionKind + ExpectedKindErr string + }{ + { + Name: "ambiguous groups, with preference order", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Resource: "my-kinds"}, + + ExpectedKinds: []unversioned.GroupVersionKind{ + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + ExpectedKindErr: "is ambiguous", + }, + + { + Name: "ambiguous groups, with explicit group match", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, + + ExpectedKinds: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + }, + + { + Name: "ambiguous groups, with ambiguous version match", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "first-group", Version: "first-version"}, + {Group: "second-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, + + ExpectedKinds: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + }, + ExpectedKindErr: "is ambiguous", + }, + } + for _, testCase := range testCases { + tcName := testCase.Name + mapper := NewDefaultRESTMapper(testCase.PreferredOrder, fakeInterfaces) + for _, kind := range testCase.KindsToRegister { + mapper.Add(kind, RESTScopeNamespace, false) + } + + actualKinds, err := mapper.KindsFor(testCase.PartialResourceToRequest) + if err != nil { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } + if !reflect.DeepEqual(testCase.ExpectedKinds, actualKinds) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds, actualKinds) + } + + singleKind, err := mapper.KindFor(testCase.PartialResourceToRequest) + if err == nil && len(testCase.ExpectedKindErr) != 0 { + t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedKindErr) + continue + } + if err != nil { + if len(testCase.ExpectedKindErr) == 0 { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } else { + if !strings.Contains(err.Error(), testCase.ExpectedKindErr) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKindErr, err) + continue + } + } + + } else { + if testCase.ExpectedKinds[0] != singleKind { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds[0], singleKind) + } + + } + } +} + +func TestRESTMapperResourcesFor(t *testing.T) { + testCases := []struct { + Name string + PreferredOrder []unversioned.GroupVersion + KindsToRegister []unversioned.GroupVersionKind + PartialResourceToRequest unversioned.GroupVersionResource + + ExpectedResources []unversioned.GroupVersionResource + ExpectedResourceErr string + }{ + { + Name: "ambiguous groups, with preference order", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Resource: "my-kinds"}, + + ExpectedResources: []unversioned.GroupVersionResource{ + {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + ExpectedResourceErr: "is ambiguous", + }, + + { + Name: "ambiguous groups, with explicit group match", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, + + ExpectedResources: []unversioned.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + }, + + { + Name: "ambiguous groups, with ambiguous version match", + PreferredOrder: []unversioned.GroupVersion{ + {Group: "first-group", Version: "first-version"}, + {Group: "second-group", Version: "first-version"}, + }, + KindsToRegister: []unversioned.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: unversioned.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, + + ExpectedResources: []unversioned.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, + }, + ExpectedResourceErr: "is ambiguous", + }, + } + for _, testCase := range testCases { + tcName := testCase.Name + mapper := NewDefaultRESTMapper(testCase.PreferredOrder, fakeInterfaces) + for _, kind := range testCase.KindsToRegister { + mapper.Add(kind, RESTScopeNamespace, false) + } + + actualResources, err := mapper.ResourcesFor(testCase.PartialResourceToRequest) + if err != nil { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } + if !reflect.DeepEqual(testCase.ExpectedResources, actualResources) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources, actualResources) + } + + singleResource, err := mapper.ResourceFor(testCase.PartialResourceToRequest) + if err == nil && len(testCase.ExpectedResourceErr) != 0 { + t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedResourceErr) + continue + } + if err != nil { + if len(testCase.ExpectedResourceErr) == 0 { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } else { + if !strings.Contains(err.Error(), testCase.ExpectedResourceErr) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResourceErr, err) + continue + } + } + + } else { + if testCase.ExpectedResources[0] != singleResource { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources[0], singleResource) + } + + } + } +} + func TestKindToResource(t *testing.T) { testCases := []struct { Kind string @@ -181,9 +408,11 @@ func TestKindToResource(t *testing.T) { {Kind: "lowercase", MixedCase: false, Plural: "lowercases", Singular: "lowercase"}, } for i, testCase := range testCases { - plural, singular := KindToResource(testCase.Kind, testCase.MixedCase) - if singular != testCase.Singular || plural != testCase.Plural { - t.Errorf("%d: unexpected plural and singular: %s %s", i, plural, singular) + version := unversioned.GroupVersion{} + + plural, singular := KindToResource(version.WithKind(testCase.Kind), testCase.MixedCase) + if singular != version.WithResource(testCase.Singular) || plural != version.WithResource(testCase.Plural) { + t.Errorf("%d: unexpected plural and singular: %v %v", i, plural, singular) } } } @@ -224,7 +453,7 @@ func TestRESTMapperResourceSingularizer(t *testing.T) { t.Errorf("%d: unexpected error: %v", i, err) } if singular != testCase.Singular { - t.Errorf("%d: mismatched singular: %s, should be %s", i, singular, testCase.Singular) + t.Errorf("%d: mismatched singular: got %v, expected %v", i, singular, testCase.Singular) } } } diff --git a/pkg/api/unversioned/group_version.go b/pkg/api/unversioned/group_version.go index 3f5e325f648..be07914f803 100644 --- a/pkg/api/unversioned/group_version.go +++ b/pkg/api/unversioned/group_version.go @@ -29,6 +29,10 @@ type GroupResource struct { Resource string } +func (gr GroupResource) WithVersion(version string) GroupVersionResource { + return GroupVersionResource{Group: gr.Group, Version: version, Resource: gr.Resource} +} + func (gr GroupResource) IsEmpty() bool { return len(gr.Group) == 0 && len(gr.Resource) == 0 } diff --git a/pkg/apis/componentconfig/install/install_test.go b/pkg/apis/componentconfig/install/install_test.go index 3a751b52039..047b77e32f6 100644 --- a/pkg/apis/componentconfig/install/install_test.go +++ b/pkg/apis/componentconfig/install/install_test.go @@ -57,7 +57,7 @@ func TestRESTMapper(t *testing.T) { gv := unversioned.GroupVersion{Group: componentconfig.GroupName, Version: "v1alpha1"} proxyGVK := gv.WithKind("KubeProxyConfiguration") - if gvk, err := latest.GroupOrDie(componentconfig.GroupName).RESTMapper.KindFor("kubeproxyconfiguration"); err != nil || gvk != proxyGVK { + if gvk, err := latest.GroupOrDie(componentconfig.GroupName).RESTMapper.KindFor(gv.WithResource("kubeproxyconfiguration")); err != nil || gvk != proxyGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } diff --git a/pkg/apis/extensions/install/install_test.go b/pkg/apis/extensions/install/install_test.go index 83504f43fc7..98435e044f6 100644 --- a/pkg/apis/extensions/install/install_test.go +++ b/pkg/apis/extensions/install/install_test.go @@ -80,7 +80,7 @@ func TestRESTMapper(t *testing.T) { hpaGVK := gv.WithKind("HorizontalPodAutoscaler") daemonSetGVK := gv.WithKind("DaemonSet") - if gvk, err := latest.GroupOrDie(extensions.GroupName).RESTMapper.KindFor("horizontalpodautoscalers"); err != nil || gvk != hpaGVK { + if gvk, err := latest.GroupOrDie(extensions.GroupName).RESTMapper.KindFor(gv.WithResource("horizontalpodautoscalers")); err != nil || gvk != hpaGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } diff --git a/pkg/client/unversioned/scale.go b/pkg/client/unversioned/scale.go index 7663325b487..4064ce4dd49 100644 --- a/pkg/client/unversioned/scale.go +++ b/pkg/client/unversioned/scale.go @@ -18,6 +18,7 @@ package unversioned import ( "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -48,17 +49,25 @@ func newScales(c *ExtensionsClient, namespace string) *scales { // Get takes the reference to scale subresource and returns the subresource or error, if one occurs. func (c *scales) Get(kind string, name string) (result *extensions.Scale, err error) { result = &extensions.Scale{} - resource, _ := meta.KindToResource(kind, false) - err = c.client.Get().Namespace(c.ns).Resource(resource).Name(name).SubResource("scale").Do().Into(result) + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind, false) + + err = c.client.Get().Namespace(c.ns).Resource(resource.Resource).Name(name).SubResource("scale").Do().Into(result) return } func (c *scales) Update(kind string, scale *extensions.Scale) (result *extensions.Scale, err error) { result = &extensions.Scale{} - resource, _ := meta.KindToResource(kind, false) + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind, false) + err = c.client.Put(). Namespace(scale.Namespace). - Resource(resource). + Resource(resource.Resource). Name(scale.Name). SubResource("scale"). Body(scale). diff --git a/pkg/client/unversioned/testclient/fixture.go b/pkg/client/unversioned/testclient/fixture.go index 73a80fab294..fad088e99de 100644 --- a/pkg/client/unversioned/testclient/fixture.go +++ b/pkg/client/unversioned/testclient/fixture.go @@ -59,7 +59,7 @@ type ObjectScheme interface { func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { return func(action Action) (bool, runtime.Object, error) { - gvk, err := mapper.KindFor(action.GetResource()) + kind, err := mapper.KindFor(unversioned.GroupVersionResource{Resource: action.GetResource()}) if err != nil { return false, nil, fmt.Errorf("unrecognized action %s: %v", action.GetResource(), err) } @@ -67,16 +67,16 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { // TODO: have mapper return a Kind for a subresource? switch castAction := action.(type) { case ListAction: - gvk.Kind += "List" - resource, err := o.Kind(gvk, "") + kind.Kind += "List" + resource, err := o.Kind(kind, "") return true, resource, err case GetAction: - resource, err := o.Kind(gvk, castAction.GetName()) + resource, err := o.Kind(kind, castAction.GetName()) return true, resource, err case DeleteAction: - resource, err := o.Kind(gvk, castAction.GetName()) + resource, err := o.Kind(kind, castAction.GetName()) return true, resource, err case CreateAction: @@ -84,7 +84,7 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { if err != nil { return true, nil, err } - resource, err := o.Kind(gvk, meta.Name) + resource, err := o.Kind(kind, meta.Name) return true, resource, err case UpdateAction: @@ -92,7 +92,7 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { if err != nil { return true, nil, err } - resource, err := o.Kind(gvk, meta.Name) + resource, err := o.Kind(kind, meta.Name) return true, resource, err default: diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go index 5ce13633a47..fadf6d6c592 100644 --- a/pkg/kubectl/cmd/explain.go +++ b/pkg/kubectl/cmd/explain.go @@ -79,7 +79,7 @@ func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } // TODO: We should deduce the group for a resource by discovering the supported resources at server. - gvk, err := mapper.KindFor(inModel) + gvk, err := mapper.KindFor(unversioned.GroupVersionResource{Resource: inModel}) if err != nil { return err } diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index 43900e9fc23..fb11b9a3cf7 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -360,12 +360,12 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg if outputFormat != "" { return f.PrintObject(cmd, newRc, out) } - gvk, err := api.Scheme.ObjectKind(newRc) + kind, err := api.Scheme.ObjectKind(newRc) if err != nil { return err } - _, res := meta.KindToResource(gvk.Kind, false) - cmdutil.PrintSuccess(mapper, false, out, res, oldName, message) + _, res := meta.KindToResource(kind, false) + cmdutil.PrintSuccess(mapper, false, out, res.Resource, oldName, message) return nil } diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index e4d91d5288a..f2f4d6a9bbb 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" ) const kubectlAnnotationPrefix = "kubectl.kubernetes.io/" @@ -80,45 +81,45 @@ var _ meta.RESTMapper = &ShortcutExpander{} // KindFor implements meta.RESTMapper. It expands the resource first, then invokes the wrapped // mapper. -func (e ShortcutExpander) KindFor(resource string) (unversioned.GroupVersionKind, error) { +func (e ShortcutExpander) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { resource = expandResourceShortcut(resource) return e.RESTMapper.KindFor(resource) } // ResourceIsValid takes a string (kind) and checks if it's a valid resource. // It expands the resource first, then invokes the wrapped mapper. -func (e ShortcutExpander) ResourceIsValid(resource string) bool { +func (e ShortcutExpander) ResourceIsValid(resource unversioned.GroupVersionResource) bool { return e.RESTMapper.ResourceIsValid(expandResourceShortcut(resource)) } // ResourceSingularizer expands the named resource and then singularizes it. func (e ShortcutExpander) ResourceSingularizer(resource string) (string, error) { - return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(resource)) + return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource) } // expandResourceShortcut will return the expanded version of resource // (something that a pkg/api/meta.RESTMapper can understand), if it is // indeed a shortcut. Otherwise, will return resource unmodified. -func expandResourceShortcut(resource string) string { - shortForms := map[string]string{ +func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { + shortForms := map[string]unversioned.GroupVersionResource{ // Please keep this alphabetized - "cs": "componentstatuses", - "ds": "daemonsets", - "ep": "endpoints", - "ev": "events", - "hpa": "horizontalpodautoscalers", - "ing": "ingresses", - "limits": "limitranges", - "no": "nodes", - "ns": "namespaces", - "po": "pods", - "pvc": "persistentvolumeclaims", - "pv": "persistentvolumes", - "quota": "resourcequotas", - "rc": "replicationcontrollers", - "svc": "services", + "cs": api.SchemeGroupVersion.WithResource("componentstatuses"), + "ds": extensions.SchemeGroupVersion.WithResource("daemonsets"), + "ep": api.SchemeGroupVersion.WithResource("endpoints"), + "ev": api.SchemeGroupVersion.WithResource("events"), + "hpa": extensions.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), + "ing": extensions.SchemeGroupVersion.WithResource("ingresses"), + "limits": api.SchemeGroupVersion.WithResource("limitranges"), + "no": api.SchemeGroupVersion.WithResource("nodes"), + "ns": api.SchemeGroupVersion.WithResource("namespaces"), + "po": api.SchemeGroupVersion.WithResource("pods"), + "pvc": api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), + "pv": api.SchemeGroupVersion.WithResource("persistentvolumes"), + "quota": api.SchemeGroupVersion.WithResource("resourcequotas"), + "rc": api.SchemeGroupVersion.WithResource("replicationcontrollers"), + "svc": api.SchemeGroupVersion.WithResource("services"), } - if expanded, ok := shortForms[resource]; ok { + if expanded, ok := shortForms[resource.Resource]; ok { return expanded } return resource diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index bba7a781890..8ac793f8225 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" @@ -431,7 +432,7 @@ func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) { } mappings := []*meta.RESTMapping{} for _, r := range b.resources { - gvk, err := b.mapper.KindFor(r) + gvk, err := b.mapper.KindFor(unversioned.GroupVersionResource{Resource: r}) if err != nil { return nil, err } @@ -451,7 +452,7 @@ func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) if _, ok := mappings[r.Resource]; ok { continue } - gvk, err := b.mapper.KindFor(r.Resource) + gvk, err := b.mapper.KindFor(unversioned.GroupVersionResource{Resource: r.Resource}) if err != nil { return nil, err } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index c2704da271e..d9e818e6b24 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -203,11 +203,24 @@ type NamePrinter struct { // and print "resource/name" pair. If the object is a List, print all items in it. func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { objvalue := reflect.ValueOf(obj).Elem() - kind := objvalue.FieldByName("Kind") - if !kind.IsValid() { - kind = reflect.ValueOf("") + kindString := objvalue.FieldByName("Kind") + groupVersionString := objvalue.FieldByName("APIVersion") + kind := unversioned.GroupVersionKind{} + if !kindString.IsValid() { + kindString = reflect.ValueOf("") } - if kind.String() == "List" { + kind.Kind = kindString.String() + + if !groupVersionString.IsValid() { + groupVersionString = reflect.ValueOf("/") + } + gv, err := unversioned.ParseGroupVersion(groupVersionString.String()) + if err != nil { + kind.Group = gv.Group + kind.Version = gv.Version + } + + if kind.Kind == "List" { items := objvalue.FieldByName("Items") if items.Type().String() == "[]runtime.RawExtension" { for i := 0; i < items.Len(); i++ { @@ -237,9 +250,9 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { if !name.IsValid() { name = reflect.ValueOf("") } - _, resource := meta.KindToResource(kind.String(), false) + _, resource := meta.KindToResource(kind, false) - fmt.Fprintf(w, "%s/%s\n", resource, name) + fmt.Fprintf(w, "%s/%s\n", resource.Resource, name) } return nil diff --git a/pkg/registry/thirdpartyresourcedata/codec.go b/pkg/registry/thirdpartyresourcedata/codec.go index e85ebfc8309..135e8efa067 100644 --- a/pkg/registry/thirdpartyresourcedata/codec.go +++ b/pkg/registry/thirdpartyresourcedata/codec.go @@ -41,14 +41,55 @@ type thirdPartyResourceDataMapper struct { var _ meta.RESTMapper = &thirdPartyResourceDataMapper{} -func (t *thirdPartyResourceDataMapper) isThirdPartyResource(resource string) bool { - plural, _ := meta.KindToResource(t.kind, false) - return resource == plural +func (t *thirdPartyResourceDataMapper) getResource() unversioned.GroupVersionResource { + plural, _ := meta.KindToResource(t.getKind(), false) + + return plural } -func (t *thirdPartyResourceDataMapper) KindFor(resource string) (unversioned.GroupVersionKind, error) { +func (t *thirdPartyResourceDataMapper) getKind() unversioned.GroupVersionKind { + return unversioned.GroupVersionKind{Group: t.group, Version: t.version, Kind: t.kind} +} + +func (t *thirdPartyResourceDataMapper) isThirdPartyResource(partialResource unversioned.GroupVersionResource) bool { + actualResource := t.getResource() + if strings.ToLower(partialResource.Resource) != strings.ToLower(actualResource.Resource) { + return false + } + if len(partialResource.Group) != 0 && partialResource.Group != actualResource.Group { + return false + } + if len(partialResource.Version) != 0 && partialResource.Version != actualResource.Version { + return false + } + + return true +} + +func (t *thirdPartyResourceDataMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { if t.isThirdPartyResource(resource) { - return unversioned.GroupVersionKind{Group: t.group, Version: t.version, Kind: t.kind}, nil + return []unversioned.GroupVersionResource{t.getResource()}, nil + } + return t.mapper.ResourcesFor(resource) +} + +func (t *thirdPartyResourceDataMapper) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { + if t.isThirdPartyResource(resource) { + return []unversioned.GroupVersionKind{t.getKind()}, nil + } + return t.mapper.KindsFor(resource) +} + +func (t *thirdPartyResourceDataMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + if t.isThirdPartyResource(resource) { + return t.getResource(), nil + } + return t.mapper.ResourceFor(resource) +} + +func (t *thirdPartyResourceDataMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + if t.isThirdPartyResource(resource) { + return t.getKind(), nil } return t.mapper.KindFor(resource) } @@ -86,8 +127,7 @@ func (t *thirdPartyResourceDataMapper) ResourceSingularizer(resource string) (si return t.mapper.ResourceSingularizer(resource) } -// ResourceIsValid takes a string (kind) and checks if it's a valid resource -func (t *thirdPartyResourceDataMapper) ResourceIsValid(resource string) bool { +func (t *thirdPartyResourceDataMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { return t.isThirdPartyResource(resource) || t.mapper.ResourceIsValid(resource) } diff --git a/pkg/registry/thirdpartyresourcedata/codec_test.go b/pkg/registry/thirdpartyresourcedata/codec_test.go index 093990f44fd..5af08ce82e3 100644 --- a/pkg/registry/thirdpartyresourcedata/codec_test.go +++ b/pkg/registry/thirdpartyresourcedata/codec_test.go @@ -212,9 +212,9 @@ func TestResourceIsValid(t *testing.T) { for _, test := range tests { mapper := &thirdPartyResourceDataMapper{kind: test.kind} mapper.mapper = api.RESTMapper - valid := mapper.ResourceIsValid(test.resource) + valid := mapper.ResourceIsValid(unversioned.GroupVersionResource{Resource: test.resource}) if valid != test.valid { - t.Errorf("expected: %v, saw: %v for %s", test.valid, valid, test.name) + t.Errorf("%s: expected: %v, actual: %v", test.name, test.valid, valid) } } } diff --git a/plugin/pkg/admission/namespace/autoprovision/admission.go b/plugin/pkg/admission/namespace/autoprovision/admission.go index f4886d6a9c4..df79f37735d 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission.go @@ -22,7 +22,6 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/client/cache" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/runtime" @@ -45,17 +44,13 @@ type provision struct { } func (p *provision) Admit(a admission.Attributes) (err error) { - gvk, err := api.RESTMapper.KindFor(a.GetResource().Resource) - if err != nil { - return admission.NewForbidden(a, err) - } - mapping, err := api.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) - if err != nil { - return admission.NewForbidden(a, err) - } - if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do + // if we're here, then the API server has found a route, which means that if we have a non-empty namespace + // its a namespaced resource. + if len(a.GetNamespace()) == 0 || a.GetKind() == api.Kind("Namespace") { return nil } + namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(), diff --git a/plugin/pkg/admission/namespace/exists/admission.go b/plugin/pkg/admission/namespace/exists/admission.go index 22de7828b3c..2f3ffe5f280 100644 --- a/plugin/pkg/admission/namespace/exists/admission.go +++ b/plugin/pkg/admission/namespace/exists/admission.go @@ -23,7 +23,6 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/client/cache" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/runtime" @@ -46,17 +45,13 @@ type exists struct { } func (e *exists) Admit(a admission.Attributes) (err error) { - gvk, err := api.RESTMapper.KindFor(a.GetResource().Resource) - if err != nil { - return errors.NewInternalError(err) - } - mapping, err := api.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) - if err != nil { - return errors.NewInternalError(err) - } - if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do + // if we're here, then the API server has found a route, which means that if we have a non-empty namespace + // its a namespaced resource. + if len(a.GetNamespace()) == 0 || a.GetKind() == api.Kind("Namespace") { return nil } + namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(), diff --git a/plugin/pkg/admission/namespace/lifecycle/admission.go b/plugin/pkg/admission/namespace/lifecycle/admission.go index 19a77c1609d..514d35f46d2 100644 --- a/plugin/pkg/admission/namespace/lifecycle/admission.go +++ b/plugin/pkg/admission/namespace/lifecycle/admission.go @@ -24,7 +24,6 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/client/cache" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/runtime" @@ -48,23 +47,18 @@ type lifecycle struct { } func (l *lifecycle) Admit(a admission.Attributes) (err error) { - // prevent deletion of immortal namespaces if a.GetOperation() == admission.Delete && a.GetKind() == api.Kind("Namespace") && l.immortalNamespaces.Has(a.GetName()) { return errors.NewForbidden(a.GetResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted")) } - gvk, err := api.RESTMapper.KindFor(a.GetResource().Resource) - if err != nil { - return errors.NewInternalError(err) - } - mapping, err := api.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) - if err != nil { - return errors.NewInternalError(err) - } - if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do + // if we're here, then the API server has found a route, which means that if we have a non-empty namespace + // its a namespaced resource. + if len(a.GetNamespace()) == 0 || a.GetKind() == api.Kind("Namespace") { return nil } + namespaceObj, exists, err := l.store.Get(&api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(),