diff --git a/pkg/api/rest/rest.go b/pkg/api/rest/rest.go index 32c78215ca2..584670f7789 100644 --- a/pkg/api/rest/rest.go +++ b/pkg/api/rest/rest.go @@ -55,6 +55,14 @@ type Storage interface { New() runtime.Object } +// KindProvider specifies a different kind for its API than for its internal storage. This is necessary for external +// objects that are not compiled into the api server. For such objects, there is no in-memory representation for +// the object, so they must be represented as generic objects (e.g. RawJSON), but when we present the object as part of +// API discovery we want to present the specific kind, not the generic internal representation. +type KindProvider interface { + Kind() string +} + // Lister is an object that can retrieve resources that match the provided field and label criteria. type Lister interface { // NewList returns an empty object that can be used with the List call. diff --git a/pkg/api/unversioned/types.go b/pkg/api/unversioned/types.go index 3c77fcfdf91..bc910a61c2c 100644 --- a/pkg/api/unversioned/types.go +++ b/pkg/api/unversioned/types.go @@ -339,6 +339,8 @@ type APIResource struct { Name string `json:"name"` // namespaced indicates if a resource is namespaced or not. Namespaced bool `json:"namespaced"` + // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') + Kind string `json:"kind"` } // APIResourceList is a list of APIResource, it is used to expose the name of the diff --git a/pkg/api/unversioned/types_swagger_doc_generated.go b/pkg/api/unversioned/types_swagger_doc_generated.go index 6633ddf4fc1..0e2180a1c30 100644 --- a/pkg/api/unversioned/types_swagger_doc_generated.go +++ b/pkg/api/unversioned/types_swagger_doc_generated.go @@ -51,6 +51,7 @@ var map_APIResource = map[string]string{ "": "APIResource specifies the name of a resource and whether it is namespaced.", "name": "name is the name of the resource.", "namespaced": "namespaced indicates if a resource is namespaced or not.", + "kind": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", } func (APIResource) SwaggerDoc() map[string]string { diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go index 819cec6a198..a2ca78e5e4d 100644 --- a/pkg/apiserver/api_installer.go +++ b/pkg/apiserver/api_installer.go @@ -152,10 +152,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag } } + kind := fqKindToRegister.Kind + if fqKindToRegister.IsEmpty() { return nil, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion) } - kind := fqKindToRegister.Kind versionedPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(kind)) if err != nil { @@ -322,6 +323,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag params := []*restful.Parameter{} actions := []action{} + var resourceKind string + kindProvider, ok := storage.(rest.KindProvider) + if ok { + resourceKind = kindProvider.Kind() + } else { + resourceKind = fqKindToRegister.Kind + } + var apiResource unversioned.APIResource // Get the list of actions for the given scope. switch scope.Name() { @@ -339,6 +348,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag } apiResource.Name = path apiResource.Namespaced = false + apiResource.Kind = resourceKind namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)} // Handler for standard REST verbs (GET, PUT, POST and DELETE). @@ -381,6 +391,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag } apiResource.Name = path apiResource.Namespaced = true + apiResource.Kind = resourceKind namer := scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), false} actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister) diff --git a/pkg/client/unversioned/client_test.go b/pkg/client/unversioned/client_test.go index 8fdfe4ecd24..487591c5da2 100644 --- a/pkg/client/unversioned/client_test.go +++ b/pkg/client/unversioned/client_test.go @@ -137,17 +137,17 @@ func TestGetServerResources(t *testing.T) { stable := unversioned.APIResourceList{ GroupVersion: "v1", APIResources: []unversioned.APIResource{ - {"pods", true}, - {"services", true}, - {"namespaces", false}, + {"pods", true, "Pod"}, + {"services", true, "Service"}, + {"namespaces", false, "Namespace"}, }, } beta := unversioned.APIResourceList{ GroupVersion: "extensions/v1", APIResources: []unversioned.APIResource{ - {"deployments", true}, - {"ingresses", true}, - {"jobs", true}, + {"deployments", true, "Deployment"}, + {"ingresses", true, "Ingress"}, + {"jobs", true, "Job"}, }, } tests := []struct { diff --git a/pkg/registry/thirdpartyresourcedata/etcd/etcd.go b/pkg/registry/thirdpartyresourcedata/etcd/etcd.go index 505cb8f7801..98e7662a9f7 100644 --- a/pkg/registry/thirdpartyresourcedata/etcd/etcd.go +++ b/pkg/registry/thirdpartyresourcedata/etcd/etcd.go @@ -33,6 +33,7 @@ import ( // REST implements a RESTStorage for ThirdPartyResourceDatas against etcd type REST struct { *etcdgeneric.Etcd + kind string } // NewREST returns a registry which will store ThirdPartyResourceData in the given helper @@ -64,5 +65,13 @@ func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator, gro Storage: storageInterface, } - return &REST{store} + return &REST{ + Etcd: store, + kind: kind, + } +} + +// Implements the rest.KindProvider interface +func (r *REST) Kind() string { + return r.kind }