diff --git a/pkg/endpoints/apiserver_test.go b/pkg/endpoints/apiserver_test.go index 9de67ad7a..b80337763 100644 --- a/pkg/endpoints/apiserver_test.go +++ b/pkg/endpoints/apiserver_test.go @@ -372,6 +372,10 @@ func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtim return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions) } +func (storate *SimpleRESTStorage) GetSingularName() string { + return "simple" +} + func (storage *SimpleRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { storage.checkContext(ctx) result := &genericapitesting.SimpleList{ @@ -575,6 +579,10 @@ func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string return s.emptyConnectOptions, false, "" } +func (s *ConnecterRESTStorage) GetSingularName() string { + return "simple" +} + type MetadataRESTStorage struct { *SimpleRESTStorage types []string @@ -619,6 +627,10 @@ type GetWithOptionsRootRESTStorage struct { takesPath string } +func (r *GetWithOptionsRootRESTStorage) GetSingularName() string { + return "simple" +} + func (r *GetWithOptionsRootRESTStorage) NamespaceScoped() bool { return false } @@ -687,6 +699,10 @@ func (storage *SimpleTypedStorage) checkContext(ctx context.Context) { storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx) } +func (storage *SimpleTypedStorage) GetSingularName() string { + return "simple" +} + func bodyOrDie(response *http.Response) string { defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) @@ -823,6 +839,10 @@ func (UnimplementedRESTStorage) New() runtime.Object { func (UnimplementedRESTStorage) Destroy() { } +func (UnimplementedRESTStorage) GetSingularName() string { + return "" +} + // TestUnimplementedRESTStorage ensures that if a rest.Storage does not implement a given // method, that it is literally not registered with the server. In the past, // we registered everything, and returned method not supported if it didn't support @@ -4289,6 +4309,10 @@ func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV sch return storage.itemGVK } +func (storage *SimpleXGSubresourceRESTStorage) GetSingularName() string { + return "simple" +} + func TestXGSubresource(t *testing.T) { container := restful.NewContainer() container.Router(restful.CurlyRouter{}) diff --git a/pkg/endpoints/installer.go b/pkg/endpoints/installer.go index 50c592775..63d217062 100644 --- a/pkg/endpoints/installer.go +++ b/pkg/endpoints/installer.go @@ -1080,9 +1080,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if categoriesProvider, ok := storage.(rest.CategoriesProvider); ok { apiResource.Categories = categoriesProvider.Categories() } - if singularNameProvider, ok := storage.(rest.SingularNameProvider); ok { - apiResource.SingularName = singularNameProvider.SingularName() + singularNameProvider, ok := storage.(rest.SingularNameProvider) + if !ok { + return nil, nil, fmt.Errorf("resource %s must implement SingularNameProvider", resource) } + apiResource.SingularName = singularNameProvider.GetSingularName() + if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok { gvk := gvkProvider.GroupVersionKind(a.group.GroupVersion) apiResource.Group = gvk.Group diff --git a/pkg/registry/generic/registry/store.go b/pkg/registry/generic/registry/store.go index 40bca4966..6e8d291a3 100644 --- a/pkg/registry/generic/registry/store.go +++ b/pkg/registry/generic/registry/store.go @@ -110,6 +110,9 @@ type Store struct { // See qualifiedResourceFromContext for details. DefaultQualifiedResource schema.GroupResource + // SingularQualifiedResource is the singular name of the resource. + SingularQualifiedResource schema.GroupResource + // KeyRootFunc returns the root etcd key for this resource; should not // include trailing "/". This is used for operations that work on the // entire collection (listing and watching). @@ -229,6 +232,8 @@ var _ rest.StandardStorage = &Store{} var _ rest.TableConvertor = &Store{} var _ GenericStore = &Store{} +var _ rest.SingularNameProvider = &Store{} + const ( OptimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again" resourceCountPollPeriodJitter = 1.2 @@ -1320,6 +1325,12 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error { if e.DefaultQualifiedResource.Empty() { return fmt.Errorf("store %#v must have a non-empty qualified resource", e) } + if e.SingularQualifiedResource.Empty() { + return fmt.Errorf("store %#v must have a non-empty singular qualified resource", e) + } + if e.DefaultQualifiedResource.Group != e.SingularQualifiedResource.Group { + return fmt.Errorf("store for %#v, singular and plural qualified resource's group name's must match", e) + } if e.NewFunc == nil { return fmt.Errorf("store for %s must have NewFunc set", e.DefaultQualifiedResource.String()) } @@ -1515,6 +1526,10 @@ func (e *Store) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { return e.ResetFieldsStrategy.GetResetFields() } +func (e *Store) GetSingularName() string { + return e.SingularQualifiedResource.Resource +} + // validateIndexers will check the prefix of indexers. func validateIndexers(indexers *cache.Indexers) error { if indexers == nil { diff --git a/pkg/registry/generic/registry/store_test.go b/pkg/registry/generic/registry/store_test.go index f7af4ec89..28fe2877c 100644 --- a/pkg/registry/generic/registry/store_test.go +++ b/pkg/registry/generic/registry/store_test.go @@ -2339,12 +2339,13 @@ func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheE } return destroyFunc, &Store{ - NewFunc: func() runtime.Object { return &example.Pod{} }, - NewListFunc: func() runtime.Object { return &example.PodList{} }, - DefaultQualifiedResource: example.Resource("pods"), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + NewFunc: func() runtime.Object { return &example.Pod{} }, + NewListFunc: func() runtime.Object { return &example.PodList{} }, + DefaultQualifiedResource: example.Resource("pods"), + SingularQualifiedResource: example.Resource("pod"), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, KeyRootFunc: func(ctx context.Context) string { return podPrefix }, diff --git a/pkg/registry/rest/rest.go b/pkg/registry/rest/rest.go index c55ae9675..b8d47ff4f 100644 --- a/pkg/registry/rest/rest.go +++ b/pkg/registry/rest/rest.go @@ -92,7 +92,7 @@ type CategoriesProvider interface { // SingularNameProvider returns singular name of resources. This is used by kubectl discovery to have singular // name representation of resources. In case of shortcut conflicts(with CRD shortcuts) singular name should always map to this resource. type SingularNameProvider interface { - SingularName() string + GetSingularName() string } // GroupVersionKindProvider is used to specify a particular GroupVersionKind to discovery. This is used for polymorphic endpoints diff --git a/pkg/server/genericapiserver_test.go b/pkg/server/genericapiserver_test.go index c6b07fc3f..ccffc8276 100644 --- a/pkg/server/genericapiserver_test.go +++ b/pkg/server/genericapiserver_test.go @@ -551,6 +551,10 @@ func (p *testGetterStorage) Get(ctx context.Context, name string, options *metav return nil, nil } +func (p *testGetterStorage) GetSingularName() string { + return "getter" +} + type testNoVerbsStorage struct { Version string } @@ -571,6 +575,10 @@ func (p *testNoVerbsStorage) New() runtime.Object { func (p *testNoVerbsStorage) Destroy() { } +func (p *testNoVerbsStorage) GetSingularName() string { + return "noverb" +} + func fakeVersion() version.Info { return version.Info{ Major: "42",