diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go index 54207b62423..75c92690f1f 100644 --- a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go @@ -41,7 +41,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*admissionregistration.MutatingWebhookConfiguration).Name, nil }, - DefaultQualifiedResource: admissionregistration.Resource("mutatingwebhookconfigurations"), + DefaultQualifiedResource: admissionregistration.Resource("mutatingwebhookconfigurations"), + SingularQualifiedResource: admissionregistration.Resource("mutatingwebhookconfiguration"), CreateStrategy: mutatingwebhookconfiguration.Strategy, UpdateStrategy: mutatingwebhookconfiguration.Strategy, diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicy/storage/storage.go b/pkg/registry/admissionregistration/validatingadmissionpolicy/storage/storage.go index 2c2f765e740..dfdf9339a17 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicy/storage/storage.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicy/storage/storage.go @@ -47,7 +47,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter, authorizer authorizer.Authori ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*admissionregistration.ValidatingAdmissionPolicy).Name, nil }, - DefaultQualifiedResource: groupResource, + DefaultQualifiedResource: groupResource, + SingularQualifiedResource: admissionregistration.Resource("validatingadmissionpolicy"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage/storage.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage/storage.go index 0f0afa4b39b..8d8b9ee9ffb 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage/storage.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage/storage.go @@ -50,7 +50,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter, authorizer authorizer.Authori ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*admissionregistration.ValidatingAdmissionPolicyBinding).Name, nil }, - DefaultQualifiedResource: groupResource, + DefaultQualifiedResource: groupResource, + SingularQualifiedResource: admissionregistration.Resource("validatingadmissionpolicybinding"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go index 507c0d9e758..6312f6a0cd0 100644 --- a/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go @@ -41,7 +41,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*admissionregistration.ValidatingWebhookConfiguration).Name, nil }, - DefaultQualifiedResource: admissionregistration.Resource("validatingwebhookconfigurations"), + DefaultQualifiedResource: admissionregistration.Resource("validatingwebhookconfigurations"), + SingularQualifiedResource: admissionregistration.Resource("validatingwebhookconfiguration"), CreateStrategy: validatingwebhookconfiguration.Strategy, UpdateStrategy: validatingwebhookconfiguration.Strategy, diff --git a/pkg/registry/apiserverinternal/storageversion/storage/storage.go b/pkg/registry/apiserverinternal/storageversion/storage/storage.go index 7bb55c31ed8..86ee40d8c95 100644 --- a/pkg/registry/apiserverinternal/storageversion/storage/storage.go +++ b/pkg/registry/apiserverinternal/storageversion/storage/storage.go @@ -45,7 +45,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*apiserverinternal.StorageVersion).Name, nil }, - DefaultQualifiedResource: apiserverinternal.Resource("storageversions"), + DefaultQualifiedResource: apiserverinternal.Resource("storageversions"), + SingularQualifiedResource: apiserverinternal.Resource("storageversion"), CreateStrategy: strategy.Strategy, UpdateStrategy: strategy.Strategy, diff --git a/pkg/registry/apps/controllerrevision/storage/storage.go b/pkg/registry/apps/controllerrevision/storage/storage.go index f00f1b80992..c4f9179aa22 100644 --- a/pkg/registry/apps/controllerrevision/storage/storage.go +++ b/pkg/registry/apps/controllerrevision/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work with ControllerRevision objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apps.ControllerRevision{} }, - NewListFunc: func() runtime.Object { return &apps.ControllerRevisionList{} }, - DefaultQualifiedResource: apps.Resource("controllerrevisions"), + NewFunc: func() runtime.Object { return &apps.ControllerRevision{} }, + NewListFunc: func() runtime.Object { return &apps.ControllerRevisionList{} }, + DefaultQualifiedResource: apps.Resource("controllerrevisions"), + SingularQualifiedResource: apps.Resource("controllerrevision"), CreateStrategy: controllerrevision.Strategy, UpdateStrategy: controllerrevision.Strategy, diff --git a/pkg/registry/apps/daemonset/storage/storage.go b/pkg/registry/apps/daemonset/storage/storage.go index 84df2450283..16e91b3e873 100644 --- a/pkg/registry/apps/daemonset/storage/storage.go +++ b/pkg/registry/apps/daemonset/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against DaemonSets. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apps.DaemonSet{} }, - NewListFunc: func() runtime.Object { return &apps.DaemonSetList{} }, - DefaultQualifiedResource: apps.Resource("daemonsets"), + NewFunc: func() runtime.Object { return &apps.DaemonSet{} }, + NewListFunc: func() runtime.Object { return &apps.DaemonSetList{} }, + DefaultQualifiedResource: apps.Resource("daemonsets"), + SingularQualifiedResource: apps.Resource("daemonset"), CreateStrategy: daemonset.Strategy, UpdateStrategy: daemonset.Strategy, diff --git a/pkg/registry/apps/deployment/storage/storage.go b/pkg/registry/apps/deployment/storage/storage.go index 8b05dd0b663..3d49c944e67 100644 --- a/pkg/registry/apps/deployment/storage/storage.go +++ b/pkg/registry/apps/deployment/storage/storage.go @@ -92,9 +92,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against deployments. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *RollbackREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apps.Deployment{} }, - NewListFunc: func() runtime.Object { return &apps.DeploymentList{} }, - DefaultQualifiedResource: apps.Resource("deployments"), + NewFunc: func() runtime.Object { return &apps.Deployment{} }, + NewListFunc: func() runtime.Object { return &apps.DeploymentList{} }, + DefaultQualifiedResource: apps.Resource("deployments"), + SingularQualifiedResource: apps.Resource("deployment"), CreateStrategy: deployment.Strategy, UpdateStrategy: deployment.Strategy, diff --git a/pkg/registry/apps/replicaset/storage/storage.go b/pkg/registry/apps/replicaset/storage/storage.go index 0c467d37327..28131b416e8 100644 --- a/pkg/registry/apps/replicaset/storage/storage.go +++ b/pkg/registry/apps/replicaset/storage/storage.go @@ -86,10 +86,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ReplicaSet. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apps.ReplicaSet{} }, - NewListFunc: func() runtime.Object { return &apps.ReplicaSetList{} }, - PredicateFunc: replicaset.MatchReplicaSet, - DefaultQualifiedResource: apps.Resource("replicasets"), + NewFunc: func() runtime.Object { return &apps.ReplicaSet{} }, + NewListFunc: func() runtime.Object { return &apps.ReplicaSetList{} }, + PredicateFunc: replicaset.MatchReplicaSet, + DefaultQualifiedResource: apps.Resource("replicasets"), + SingularQualifiedResource: apps.Resource("replicaset"), CreateStrategy: replicaset.Strategy, UpdateStrategy: replicaset.Strategy, diff --git a/pkg/registry/apps/statefulset/storage/storage.go b/pkg/registry/apps/statefulset/storage/storage.go index 47558534cdd..73bd391b259 100644 --- a/pkg/registry/apps/statefulset/storage/storage.go +++ b/pkg/registry/apps/statefulset/storage/storage.go @@ -84,9 +84,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against statefulsets. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apps.StatefulSet{} }, - NewListFunc: func() runtime.Object { return &apps.StatefulSetList{} }, - DefaultQualifiedResource: apps.Resource("statefulsets"), + NewFunc: func() runtime.Object { return &apps.StatefulSet{} }, + NewListFunc: func() runtime.Object { return &apps.StatefulSetList{} }, + DefaultQualifiedResource: apps.Resource("statefulsets"), + SingularQualifiedResource: apps.Resource("statefulset"), CreateStrategy: statefulset.Strategy, UpdateStrategy: statefulset.Strategy, diff --git a/pkg/registry/authentication/selfsubjectreview/rest.go b/pkg/registry/authentication/selfsubjectreview/rest.go index 8d502315dfe..9c1978104c0 100644 --- a/pkg/registry/authentication/selfsubjectreview/rest.go +++ b/pkg/registry/authentication/selfsubjectreview/rest.go @@ -93,3 +93,9 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return selfSAR, nil } + +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "selfsubjectrulesreview" +} diff --git a/pkg/registry/authentication/tokenreview/storage.go b/pkg/registry/authentication/tokenreview/storage.go index 46f3e00f1ac..1c9c328603f 100644 --- a/pkg/registry/authentication/tokenreview/storage.go +++ b/pkg/registry/authentication/tokenreview/storage.go @@ -60,6 +60,12 @@ func (r *REST) Destroy() { // here explicitly. } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "tokenreview" +} + func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { tokenReview, ok := obj.(*authentication.TokenReview) if !ok { diff --git a/pkg/registry/authorization/localsubjectaccessreview/rest.go b/pkg/registry/authorization/localsubjectaccessreview/rest.go index e28b03dc368..78389075064 100644 --- a/pkg/registry/authorization/localsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/localsubjectaccessreview/rest.go @@ -53,6 +53,12 @@ func (r *REST) Destroy() { // here explicitly. } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "localsubjectaccessreview" +} + func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview) if !ok { diff --git a/pkg/registry/authorization/selfsubjectaccessreview/rest.go b/pkg/registry/authorization/selfsubjectaccessreview/rest.go index f2c3a9a0b3c..f5e6be5227a 100644 --- a/pkg/registry/authorization/selfsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/selfsubjectaccessreview/rest.go @@ -53,6 +53,12 @@ func (r *REST) Destroy() { // here explicitly. } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "selfsubjectaccessreview" +} + func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview) if !ok { diff --git a/pkg/registry/authorization/selfsubjectrulesreview/rest.go b/pkg/registry/authorization/selfsubjectrulesreview/rest.go index f1c3a9f86c7..2065d2897fd 100644 --- a/pkg/registry/authorization/selfsubjectrulesreview/rest.go +++ b/pkg/registry/authorization/selfsubjectrulesreview/rest.go @@ -95,6 +95,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return ret, nil } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "selfsubjectrulesreview" +} + func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizationapi.ResourceRule { rules := make([]authorizationapi.ResourceRule, len(infos)) for i, info := range infos { diff --git a/pkg/registry/authorization/subjectaccessreview/rest.go b/pkg/registry/authorization/subjectaccessreview/rest.go index 4bb2072a5f7..5a6edb56451 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest.go +++ b/pkg/registry/authorization/subjectaccessreview/rest.go @@ -52,6 +52,12 @@ func (r *REST) Destroy() { // here explicitly. } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return "subjectaccessreview" +} + func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go index 03171ed1ff0..bedd6814756 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against horizontal pod autoscalers. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &autoscaling.HorizontalPodAutoscaler{} }, - NewListFunc: func() runtime.Object { return &autoscaling.HorizontalPodAutoscalerList{} }, - DefaultQualifiedResource: autoscaling.Resource("horizontalpodautoscalers"), + NewFunc: func() runtime.Object { return &autoscaling.HorizontalPodAutoscaler{} }, + NewListFunc: func() runtime.Object { return &autoscaling.HorizontalPodAutoscalerList{} }, + DefaultQualifiedResource: autoscaling.Resource("horizontalpodautoscalers"), + SingularQualifiedResource: autoscaling.Resource("horizontalpodautoscaler"), CreateStrategy: horizontalpodautoscaler.Strategy, UpdateStrategy: horizontalpodautoscaler.Strategy, diff --git a/pkg/registry/batch/cronjob/storage/storage.go b/pkg/registry/batch/cronjob/storage/storage.go index 637dc8f3754..a963d140517 100644 --- a/pkg/registry/batch/cronjob/storage/storage.go +++ b/pkg/registry/batch/cronjob/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against CronJobs. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &batch.CronJob{} }, - NewListFunc: func() runtime.Object { return &batch.CronJobList{} }, - DefaultQualifiedResource: batch.Resource("cronjobs"), + NewFunc: func() runtime.Object { return &batch.CronJob{} }, + NewListFunc: func() runtime.Object { return &batch.CronJobList{} }, + DefaultQualifiedResource: batch.Resource("cronjobs"), + SingularQualifiedResource: batch.Resource("cronjob"), CreateStrategy: cronjob.Strategy, UpdateStrategy: cronjob.Strategy, diff --git a/pkg/registry/batch/job/storage/storage.go b/pkg/registry/batch/job/storage/storage.go index f6cfa679536..9ca9d5f974a 100644 --- a/pkg/registry/batch/job/storage/storage.go +++ b/pkg/registry/batch/job/storage/storage.go @@ -64,10 +64,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against Jobs. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &batch.Job{} }, - NewListFunc: func() runtime.Object { return &batch.JobList{} }, - PredicateFunc: job.MatchJob, - DefaultQualifiedResource: batch.Resource("jobs"), + NewFunc: func() runtime.Object { return &batch.Job{} }, + NewListFunc: func() runtime.Object { return &batch.JobList{} }, + PredicateFunc: job.MatchJob, + DefaultQualifiedResource: batch.Resource("jobs"), + SingularQualifiedResource: batch.Resource("job"), CreateStrategy: job.Strategy, UpdateStrategy: job.Strategy, diff --git a/pkg/registry/certificates/certificates/storage/storage.go b/pkg/registry/certificates/certificates/storage/storage.go index 8de7828efde..d45391924f2 100644 --- a/pkg/registry/certificates/certificates/storage/storage.go +++ b/pkg/registry/certificates/certificates/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a registry which will store CertificateSigningRequest in the given helper. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *ApprovalREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &certificates.CertificateSigningRequest{} }, - NewListFunc: func() runtime.Object { return &certificates.CertificateSigningRequestList{} }, - DefaultQualifiedResource: certificates.Resource("certificatesigningrequests"), + NewFunc: func() runtime.Object { return &certificates.CertificateSigningRequest{} }, + NewListFunc: func() runtime.Object { return &certificates.CertificateSigningRequestList{} }, + DefaultQualifiedResource: certificates.Resource("certificatesigningrequests"), + SingularQualifiedResource: certificates.Resource("certificatesigningrequest"), CreateStrategy: csrregistry.Strategy, UpdateStrategy: csrregistry.Strategy, diff --git a/pkg/registry/coordination/lease/storage/storage.go b/pkg/registry/coordination/lease/storage/storage.go index 8989a293a6d..5b890bcf507 100644 --- a/pkg/registry/coordination/lease/storage/storage.go +++ b/pkg/registry/coordination/lease/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against leases. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &coordinationapi.Lease{} }, - NewListFunc: func() runtime.Object { return &coordinationapi.LeaseList{} }, - DefaultQualifiedResource: coordinationapi.Resource("leases"), + NewFunc: func() runtime.Object { return &coordinationapi.Lease{} }, + NewListFunc: func() runtime.Object { return &coordinationapi.LeaseList{} }, + DefaultQualifiedResource: coordinationapi.Resource("leases"), + SingularQualifiedResource: coordinationapi.Resource("lease"), CreateStrategy: lease.Strategy, UpdateStrategy: lease.Strategy, diff --git a/pkg/registry/core/componentstatus/rest.go b/pkg/registry/core/componentstatus/rest.go index 22fe41a0c84..4539217a674 100644 --- a/pkg/registry/core/componentstatus/rest.go +++ b/pkg/registry/core/componentstatus/rest.go @@ -58,6 +58,12 @@ func (rs *REST) New() runtime.Object { return &api.ComponentStatus{} } +var _ rest.SingularNameProvider = &REST{} + +func (rs *REST) GetSingularName() string { + return "componentstatus" +} + // Destroy cleans up resources on shutdown. func (r *REST) Destroy() { // Given no underlying store, we don't destroy anything diff --git a/pkg/registry/core/configmap/storage/storage.go b/pkg/registry/core/configmap/storage/storage.go index 8180c56e830..14e64a53936 100644 --- a/pkg/registry/core/configmap/storage/storage.go +++ b/pkg/registry/core/configmap/storage/storage.go @@ -37,10 +37,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work with ConfigMap objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.ConfigMap{} }, - NewListFunc: func() runtime.Object { return &api.ConfigMapList{} }, - PredicateFunc: configmap.Matcher, - DefaultQualifiedResource: api.Resource("configmaps"), + NewFunc: func() runtime.Object { return &api.ConfigMap{} }, + NewListFunc: func() runtime.Object { return &api.ConfigMapList{} }, + PredicateFunc: configmap.Matcher, + DefaultQualifiedResource: api.Resource("configmaps"), + SingularQualifiedResource: api.Resource("configmap"), CreateStrategy: configmap.Strategy, UpdateStrategy: configmap.Strategy, diff --git a/pkg/registry/core/endpoint/storage/storage.go b/pkg/registry/core/endpoint/storage/storage.go index ff082994cea..a3c31d3eb36 100644 --- a/pkg/registry/core/endpoint/storage/storage.go +++ b/pkg/registry/core/endpoint/storage/storage.go @@ -36,9 +36,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against endpoints. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Endpoints{} }, - NewListFunc: func() runtime.Object { return &api.EndpointsList{} }, - DefaultQualifiedResource: api.Resource("endpoints"), + NewFunc: func() runtime.Object { return &api.Endpoints{} }, + NewListFunc: func() runtime.Object { return &api.EndpointsList{} }, + DefaultQualifiedResource: api.Resource("endpoints"), + SingularQualifiedResource: api.Resource("endpoints"), CreateStrategy: endpoint.Strategy, UpdateStrategy: endpoint.Strategy, diff --git a/pkg/registry/core/event/storage/storage.go b/pkg/registry/core/event/storage/storage.go index ee4498934d1..dd2e403c1bd 100644 --- a/pkg/registry/core/event/storage/storage.go +++ b/pkg/registry/core/event/storage/storage.go @@ -42,7 +42,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter, ttl uint64) (*REST, error) { TTLFunc: func(runtime.Object, uint64, bool) (uint64, error) { return ttl, nil }, - DefaultQualifiedResource: api.Resource("events"), + DefaultQualifiedResource: api.Resource("events"), + SingularQualifiedResource: api.Resource("event"), CreateStrategy: event.Strategy, UpdateStrategy: event.Strategy, diff --git a/pkg/registry/core/limitrange/storage/storage.go b/pkg/registry/core/limitrange/storage/storage.go index b3fd401f701..d91f14204b3 100644 --- a/pkg/registry/core/limitrange/storage/storage.go +++ b/pkg/registry/core/limitrange/storage/storage.go @@ -33,9 +33,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against limit ranges. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.LimitRange{} }, - NewListFunc: func() runtime.Object { return &api.LimitRangeList{} }, - DefaultQualifiedResource: api.Resource("limitranges"), + NewFunc: func() runtime.Object { return &api.LimitRange{} }, + NewListFunc: func() runtime.Object { return &api.LimitRangeList{} }, + DefaultQualifiedResource: api.Resource("limitranges"), + SingularQualifiedResource: api.Resource("limitrange"), CreateStrategy: limitrange.Strategy, UpdateStrategy: limitrange.Strategy, diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index 37b130092ca..e88fad7fa8a 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -59,10 +59,11 @@ type FinalizeREST struct { // NewREST returns a RESTStorage object that will work against namespaces. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *FinalizeREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Namespace{} }, - NewListFunc: func() runtime.Object { return &api.NamespaceList{} }, - PredicateFunc: namespace.MatchNamespace, - DefaultQualifiedResource: api.Resource("namespaces"), + NewFunc: func() runtime.Object { return &api.Namespace{} }, + NewListFunc: func() runtime.Object { return &api.NamespaceList{} }, + PredicateFunc: namespace.MatchNamespace, + DefaultQualifiedResource: api.Resource("namespaces"), + SingularQualifiedResource: api.Resource("namespace"), CreateStrategy: namespace.Strategy, UpdateStrategy: namespace.Strategy, @@ -94,6 +95,12 @@ func (r *REST) NamespaceScoped() bool { return r.store.NamespaceScoped() } +var _ rest.SingularNameProvider = &REST{} + +func (r *REST) GetSingularName() string { + return r.store.GetSingularName() +} + func (r *REST) New() runtime.Object { return r.store.New() } diff --git a/pkg/registry/core/node/storage/storage.go b/pkg/registry/core/node/storage/storage.go index 56e806770f4..912dcb84171 100644 --- a/pkg/registry/core/node/storage/storage.go +++ b/pkg/registry/core/node/storage/storage.go @@ -96,10 +96,11 @@ func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, // NewStorage returns a NodeStorage object that will work against nodes. func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client.KubeletClientConfig, proxyTransport http.RoundTripper) (*NodeStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Node{} }, - NewListFunc: func() runtime.Object { return &api.NodeList{} }, - PredicateFunc: node.MatchNode, - DefaultQualifiedResource: api.Resource("nodes"), + NewFunc: func() runtime.Object { return &api.Node{} }, + NewListFunc: func() runtime.Object { return &api.NodeList{} }, + PredicateFunc: node.MatchNode, + DefaultQualifiedResource: api.Resource("nodes"), + SingularQualifiedResource: api.Resource("node"), CreateStrategy: node.Strategy, UpdateStrategy: node.Strategy, diff --git a/pkg/registry/core/persistentvolume/storage/storage.go b/pkg/registry/core/persistentvolume/storage/storage.go index df65eabc879..41422229bcf 100644 --- a/pkg/registry/core/persistentvolume/storage/storage.go +++ b/pkg/registry/core/persistentvolume/storage/storage.go @@ -40,10 +40,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against persistent volumes. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.PersistentVolume{} }, - NewListFunc: func() runtime.Object { return &api.PersistentVolumeList{} }, - PredicateFunc: persistentvolume.MatchPersistentVolumes, - DefaultQualifiedResource: api.Resource("persistentvolumes"), + NewFunc: func() runtime.Object { return &api.PersistentVolume{} }, + NewListFunc: func() runtime.Object { return &api.PersistentVolumeList{} }, + PredicateFunc: persistentvolume.MatchPersistentVolumes, + DefaultQualifiedResource: api.Resource("persistentvolumes"), + SingularQualifiedResource: api.Resource("persistentvolume"), CreateStrategy: persistentvolume.Strategy, UpdateStrategy: persistentvolume.Strategy, diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage.go b/pkg/registry/core/persistentvolumeclaim/storage/storage.go index d472bbd8ef3..4ba7b954242 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage.go @@ -41,10 +41,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against persistent volume claims. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.PersistentVolumeClaim{} }, - NewListFunc: func() runtime.Object { return &api.PersistentVolumeClaimList{} }, - PredicateFunc: persistentvolumeclaim.MatchPersistentVolumeClaim, - DefaultQualifiedResource: api.Resource("persistentvolumeclaims"), + NewFunc: func() runtime.Object { return &api.PersistentVolumeClaim{} }, + NewListFunc: func() runtime.Object { return &api.PersistentVolumeClaimList{} }, + PredicateFunc: persistentvolumeclaim.MatchPersistentVolumeClaim, + DefaultQualifiedResource: api.Resource("persistentvolumeclaims"), + SingularQualifiedResource: api.Resource("persistentvolumeclaim"), CreateStrategy: persistentvolumeclaim.Strategy, UpdateStrategy: persistentvolumeclaim.Strategy, diff --git a/pkg/registry/core/pod/storage/storage.go b/pkg/registry/core/pod/storage/storage.go index 3960918de62..723ef4778e9 100644 --- a/pkg/registry/core/pod/storage/storage.go +++ b/pkg/registry/core/pod/storage/storage.go @@ -73,10 +73,11 @@ type REST struct { func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) (PodStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Pod{} }, - NewListFunc: func() runtime.Object { return &api.PodList{} }, - PredicateFunc: registrypod.MatchPod, - DefaultQualifiedResource: api.Resource("pods"), + NewFunc: func() runtime.Object { return &api.Pod{} }, + NewListFunc: func() runtime.Object { return &api.PodList{} }, + PredicateFunc: registrypod.MatchPod, + DefaultQualifiedResource: api.Resource("pods"), + SingularQualifiedResource: api.Resource("pod"), CreateStrategy: registrypod.Strategy, UpdateStrategy: registrypod.Strategy, @@ -288,6 +289,10 @@ func (r *LegacyBindingREST) Create(ctx context.Context, obj runtime.Object, crea return r.bindingRest.Create(ctx, metadata.GetName(), obj, createValidation, options) } +func (r *LegacyBindingREST) GetSingularName() string { + return "binding" +} + // StatusREST implements the REST endpoint for changing the status of a pod. type StatusREST struct { store *genericregistry.Store diff --git a/pkg/registry/core/podtemplate/storage/storage.go b/pkg/registry/core/podtemplate/storage/storage.go index d7dca23868a..dfc160569d0 100644 --- a/pkg/registry/core/podtemplate/storage/storage.go +++ b/pkg/registry/core/podtemplate/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against pod templates. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.PodTemplate{} }, - NewListFunc: func() runtime.Object { return &api.PodTemplateList{} }, - DefaultQualifiedResource: api.Resource("podtemplates"), + NewFunc: func() runtime.Object { return &api.PodTemplate{} }, + NewListFunc: func() runtime.Object { return &api.PodTemplateList{} }, + DefaultQualifiedResource: api.Resource("podtemplates"), + SingularQualifiedResource: api.Resource("podtemplate"), CreateStrategy: podtemplate.Strategy, UpdateStrategy: podtemplate.Strategy, diff --git a/pkg/registry/core/replicationcontroller/storage/storage.go b/pkg/registry/core/replicationcontroller/storage/storage.go index 39a59f4a73e..edc5c7c8dd0 100644 --- a/pkg/registry/core/replicationcontroller/storage/storage.go +++ b/pkg/registry/core/replicationcontroller/storage/storage.go @@ -82,10 +82,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against replication controllers. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.ReplicationController{} }, - NewListFunc: func() runtime.Object { return &api.ReplicationControllerList{} }, - PredicateFunc: replicationcontroller.MatchController, - DefaultQualifiedResource: api.Resource("replicationcontrollers"), + NewFunc: func() runtime.Object { return &api.ReplicationController{} }, + NewListFunc: func() runtime.Object { return &api.ReplicationControllerList{} }, + PredicateFunc: replicationcontroller.MatchController, + DefaultQualifiedResource: api.Resource("replicationcontrollers"), + SingularQualifiedResource: api.Resource("replicationcontroller"), CreateStrategy: replicationcontroller.Strategy, UpdateStrategy: replicationcontroller.Strategy, diff --git a/pkg/registry/core/resourcequota/storage/storage.go b/pkg/registry/core/resourcequota/storage/storage.go index d438ec792e2..c67038f1df4 100644 --- a/pkg/registry/core/resourcequota/storage/storage.go +++ b/pkg/registry/core/resourcequota/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against resource quotas. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.ResourceQuota{} }, - NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} }, - DefaultQualifiedResource: api.Resource("resourcequotas"), + NewFunc: func() runtime.Object { return &api.ResourceQuota{} }, + NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} }, + DefaultQualifiedResource: api.Resource("resourcequotas"), + SingularQualifiedResource: api.Resource("resourcequota"), CreateStrategy: resourcequota.Strategy, UpdateStrategy: resourcequota.Strategy, diff --git a/pkg/registry/core/secret/storage/storage.go b/pkg/registry/core/secret/storage/storage.go index 81e2892df57..c329c414761 100644 --- a/pkg/registry/core/secret/storage/storage.go +++ b/pkg/registry/core/secret/storage/storage.go @@ -36,10 +36,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against secrets. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Secret{} }, - NewListFunc: func() runtime.Object { return &api.SecretList{} }, - PredicateFunc: secret.Matcher, - DefaultQualifiedResource: api.Resource("secrets"), + NewFunc: func() runtime.Object { return &api.Secret{} }, + NewListFunc: func() runtime.Object { return &api.SecretList{} }, + PredicateFunc: secret.Matcher, + DefaultQualifiedResource: api.Resource("secrets"), + SingularQualifiedResource: api.Resource("secret"), CreateStrategy: secret.Strategy, UpdateStrategy: secret.Strategy, diff --git a/pkg/registry/core/service/storage/storage.go b/pkg/registry/core/service/storage/storage.go index 17c14e63b9b..880b0127bf7 100644 --- a/pkg/registry/core/service/storage/storage.go +++ b/pkg/registry/core/service/storage/storage.go @@ -86,10 +86,11 @@ func NewREST( proxyTransport http.RoundTripper) (*REST, *StatusREST, *svcreg.ProxyREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.Service{} }, - NewListFunc: func() runtime.Object { return &api.ServiceList{} }, - DefaultQualifiedResource: api.Resource("services"), - ReturnDeletedObject: true, + NewFunc: func() runtime.Object { return &api.Service{} }, + NewListFunc: func() runtime.Object { return &api.ServiceList{} }, + DefaultQualifiedResource: api.Resource("services"), + SingularQualifiedResource: api.Resource("service"), + ReturnDeletedObject: true, CreateStrategy: svcreg.Strategy, UpdateStrategy: svcreg.Strategy, diff --git a/pkg/registry/core/serviceaccount/storage/storage.go b/pkg/registry/core/serviceaccount/storage/storage.go index 60f83b74133..0bdcf07282d 100644 --- a/pkg/registry/core/serviceaccount/storage/storage.go +++ b/pkg/registry/core/serviceaccount/storage/storage.go @@ -41,9 +41,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against service accounts. func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator, auds authenticator.Audiences, max time.Duration, podStorage, secretStorage *genericregistry.Store, extendExpiration bool) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &api.ServiceAccount{} }, - NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} }, - DefaultQualifiedResource: api.Resource("serviceaccounts"), + NewFunc: func() runtime.Object { return &api.ServiceAccount{} }, + NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} }, + DefaultQualifiedResource: api.Resource("serviceaccounts"), + SingularQualifiedResource: api.Resource("serviceaccount"), CreateStrategy: serviceaccount.Strategy, UpdateStrategy: serviceaccount.Strategy, diff --git a/pkg/registry/discovery/endpointslice/storage/storage.go b/pkg/registry/discovery/endpointslice/storage/storage.go index 756553ad580..2a0286562e3 100644 --- a/pkg/registry/discovery/endpointslice/storage/storage.go +++ b/pkg/registry/discovery/endpointslice/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against endpoint slices. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &discovery.EndpointSlice{} }, - NewListFunc: func() runtime.Object { return &discovery.EndpointSliceList{} }, - DefaultQualifiedResource: discovery.Resource("endpointslices"), + NewFunc: func() runtime.Object { return &discovery.EndpointSlice{} }, + NewListFunc: func() runtime.Object { return &discovery.EndpointSliceList{} }, + DefaultQualifiedResource: discovery.Resource("endpointslices"), + SingularQualifiedResource: discovery.Resource("endpointslice"), CreateStrategy: endpointslice.Strategy, UpdateStrategy: endpointslice.Strategy, diff --git a/pkg/registry/flowcontrol/flowschema/storage/storage.go b/pkg/registry/flowcontrol/flowschema/storage/storage.go index d1d287e735f..5a1146201a6 100644 --- a/pkg/registry/flowcontrol/flowschema/storage/storage.go +++ b/pkg/registry/flowcontrol/flowschema/storage/storage.go @@ -46,9 +46,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against flow schemas. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &flowcontrol.FlowSchema{} }, - NewListFunc: func() runtime.Object { return &flowcontrol.FlowSchemaList{} }, - DefaultQualifiedResource: flowcontrol.Resource("flowschemas"), + NewFunc: func() runtime.Object { return &flowcontrol.FlowSchema{} }, + NewListFunc: func() runtime.Object { return &flowcontrol.FlowSchemaList{} }, + DefaultQualifiedResource: flowcontrol.Resource("flowschemas"), + SingularQualifiedResource: flowcontrol.Resource("flowschema"), CreateStrategy: flowschema.Strategy, UpdateStrategy: flowschema.Strategy, diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go index defbd608b66..2149b5906f4 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go @@ -46,9 +46,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against priority level configuration. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfiguration{} }, - NewListFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfigurationList{} }, - DefaultQualifiedResource: flowcontrol.Resource("prioritylevelconfigurations"), + NewFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfiguration{} }, + NewListFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfigurationList{} }, + DefaultQualifiedResource: flowcontrol.Resource("prioritylevelconfigurations"), + SingularQualifiedResource: flowcontrol.Resource("prioritylevelconfiguration"), CreateStrategy: prioritylevelconfiguration.Strategy, UpdateStrategy: prioritylevelconfiguration.Strategy, diff --git a/pkg/registry/networking/clustercidr/storage/storage.go b/pkg/registry/networking/clustercidr/storage/storage.go index 36e93efc069..3c0f44b9ff8 100644 --- a/pkg/registry/networking/clustercidr/storage/storage.go +++ b/pkg/registry/networking/clustercidr/storage/storage.go @@ -36,9 +36,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ClusterCIDRs. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &networkingapi.ClusterCIDR{} }, - NewListFunc: func() runtime.Object { return &networkingapi.ClusterCIDRList{} }, - DefaultQualifiedResource: networkingapi.Resource("clustercidrs"), + NewFunc: func() runtime.Object { return &networkingapi.ClusterCIDR{} }, + NewListFunc: func() runtime.Object { return &networkingapi.ClusterCIDRList{} }, + DefaultQualifiedResource: networkingapi.Resource("clustercidrs"), + SingularQualifiedResource: networkingapi.Resource("clustercidr"), CreateStrategy: clustercidr.Strategy, UpdateStrategy: clustercidr.Strategy, diff --git a/pkg/registry/networking/ingress/storage/storage.go b/pkg/registry/networking/ingress/storage/storage.go index 71ab37c455e..9018055af76 100644 --- a/pkg/registry/networking/ingress/storage/storage.go +++ b/pkg/registry/networking/ingress/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against replication controllers. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &networking.Ingress{} }, - NewListFunc: func() runtime.Object { return &networking.IngressList{} }, - DefaultQualifiedResource: networking.Resource("ingresses"), + NewFunc: func() runtime.Object { return &networking.Ingress{} }, + NewListFunc: func() runtime.Object { return &networking.IngressList{} }, + DefaultQualifiedResource: networking.Resource("ingresses"), + SingularQualifiedResource: networking.Resource("ingress"), CreateStrategy: ingress.Strategy, UpdateStrategy: ingress.Strategy, diff --git a/pkg/registry/networking/ingressclass/storage/storage.go b/pkg/registry/networking/ingressclass/storage/storage.go index 187c7317cba..932b0aba90d 100644 --- a/pkg/registry/networking/ingressclass/storage/storage.go +++ b/pkg/registry/networking/ingressclass/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against replication controllers. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &networking.IngressClass{} }, - NewListFunc: func() runtime.Object { return &networking.IngressClassList{} }, - DefaultQualifiedResource: networking.Resource("ingressclasses"), + NewFunc: func() runtime.Object { return &networking.IngressClass{} }, + NewListFunc: func() runtime.Object { return &networking.IngressClassList{} }, + DefaultQualifiedResource: networking.Resource("ingressclasses"), + SingularQualifiedResource: networking.Resource("ingressclass"), CreateStrategy: ingressclass.Strategy, UpdateStrategy: ingressclass.Strategy, diff --git a/pkg/registry/networking/networkpolicy/storage/storage.go b/pkg/registry/networking/networkpolicy/storage/storage.go index 0101077b662..0445c98a4c2 100644 --- a/pkg/registry/networking/networkpolicy/storage/storage.go +++ b/pkg/registry/networking/networkpolicy/storage/storage.go @@ -41,9 +41,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against NetworkPolicies. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &networkingapi.NetworkPolicy{} }, - NewListFunc: func() runtime.Object { return &networkingapi.NetworkPolicyList{} }, - DefaultQualifiedResource: networkingapi.Resource("networkpolicies"), + NewFunc: func() runtime.Object { return &networkingapi.NetworkPolicy{} }, + NewListFunc: func() runtime.Object { return &networkingapi.NetworkPolicyList{} }, + DefaultQualifiedResource: networkingapi.Resource("networkpolicies"), + SingularQualifiedResource: networkingapi.Resource("networkpolicy"), CreateStrategy: networkpolicy.Strategy, UpdateStrategy: networkpolicy.Strategy, diff --git a/pkg/registry/node/runtimeclass/storage/storage.go b/pkg/registry/node/runtimeclass/storage/storage.go index 0d2fb4ae932..1848780ec04 100644 --- a/pkg/registry/node/runtimeclass/storage/storage.go +++ b/pkg/registry/node/runtimeclass/storage/storage.go @@ -40,7 +40,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*node.RuntimeClass).Name, nil }, - DefaultQualifiedResource: node.Resource("runtimeclasses"), + DefaultQualifiedResource: node.Resource("runtimeclasses"), + SingularQualifiedResource: node.Resource("runtimeclass"), CreateStrategy: runtimeclass.Strategy, UpdateStrategy: runtimeclass.Strategy, diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage.go b/pkg/registry/policy/poddisruptionbudget/storage/storage.go index 4af2ea2429f..0c7b1ba1f7b 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against pod disruption budgets. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &policyapi.PodDisruptionBudget{} }, - NewListFunc: func() runtime.Object { return &policyapi.PodDisruptionBudgetList{} }, - DefaultQualifiedResource: policyapi.Resource("poddisruptionbudgets"), + NewFunc: func() runtime.Object { return &policyapi.PodDisruptionBudget{} }, + NewListFunc: func() runtime.Object { return &policyapi.PodDisruptionBudgetList{} }, + DefaultQualifiedResource: policyapi.Resource("poddisruptionbudgets"), + SingularQualifiedResource: policyapi.Resource("poddisruptionbudget"), CreateStrategy: poddisruptionbudget.Strategy, UpdateStrategy: poddisruptionbudget.Strategy, diff --git a/pkg/registry/policy/podsecuritypolicy/storage/storage.go b/pkg/registry/policy/podsecuritypolicy/storage/storage.go index 1b916ad6ce9..2b2ab96cc32 100644 --- a/pkg/registry/policy/podsecuritypolicy/storage/storage.go +++ b/pkg/registry/policy/podsecuritypolicy/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against PodSecurityPolicy objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &policy.PodSecurityPolicy{} }, - NewListFunc: func() runtime.Object { return &policy.PodSecurityPolicyList{} }, - DefaultQualifiedResource: policy.Resource("podsecuritypolicies"), + NewFunc: func() runtime.Object { return &policy.PodSecurityPolicy{} }, + NewListFunc: func() runtime.Object { return &policy.PodSecurityPolicyList{} }, + DefaultQualifiedResource: policy.Resource("podsecuritypolicies"), + SingularQualifiedResource: policy.Resource("podsecuritypolicy"), CreateStrategy: podsecuritypolicy.Strategy, UpdateStrategy: podsecuritypolicy.Strategy, diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index 047630732d9..260f75eb02f 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -124,3 +124,13 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec func hasAggregationRule(clusterRole *rbac.ClusterRole) bool { return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 } + +var _ rest.SingularNameProvider = &Storage{} + +func (s *Storage) GetSingularName() string { + snp, ok := s.StandardStorage.(rest.SingularNameProvider) + if !ok { + return "" + } + return snp.GetSingularName() +} diff --git a/pkg/registry/rbac/clusterrole/storage/storage.go b/pkg/registry/rbac/clusterrole/storage/storage.go index 59347f767a4..02e2c8252e3 100644 --- a/pkg/registry/rbac/clusterrole/storage/storage.go +++ b/pkg/registry/rbac/clusterrole/storage/storage.go @@ -33,9 +33,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ClusterRole objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &rbac.ClusterRole{} }, - NewListFunc: func() runtime.Object { return &rbac.ClusterRoleList{} }, - DefaultQualifiedResource: rbac.Resource("clusterroles"), + NewFunc: func() runtime.Object { return &rbac.ClusterRole{} }, + NewListFunc: func() runtime.Object { return &rbac.ClusterRoleList{} }, + DefaultQualifiedResource: rbac.Resource("clusterroles"), + SingularQualifiedResource: rbac.Resource("clusterrole"), CreateStrategy: clusterrole.Strategy, UpdateStrategy: clusterrole.Strategy, diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go index ad2de28e008..3e4b9d725f5 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go @@ -127,3 +127,13 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation, forceAllowCreate, options) } + +var _ rest.SingularNameProvider = &Storage{} + +func (s *Storage) GetSingularName() string { + snp, ok := s.StandardStorage.(rest.SingularNameProvider) + if !ok { + return "" + } + return snp.GetSingularName() +} diff --git a/pkg/registry/rbac/clusterrolebinding/storage/storage.go b/pkg/registry/rbac/clusterrolebinding/storage/storage.go index 762730788b6..dad84e2a1a7 100644 --- a/pkg/registry/rbac/clusterrolebinding/storage/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ClusterRoleBinding objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &rbac.ClusterRoleBinding{} }, - NewListFunc: func() runtime.Object { return &rbac.ClusterRoleBindingList{} }, - DefaultQualifiedResource: rbac.Resource("clusterrolebindings"), + NewFunc: func() runtime.Object { return &rbac.ClusterRoleBinding{} }, + NewListFunc: func() runtime.Object { return &rbac.ClusterRoleBindingList{} }, + DefaultQualifiedResource: rbac.Resource("clusterrolebindings"), + SingularQualifiedResource: rbac.Resource("clusterrolebinding"), CreateStrategy: clusterrolebinding.Strategy, UpdateStrategy: clusterrolebinding.Strategy, diff --git a/pkg/registry/rbac/role/policybased/storage.go b/pkg/registry/rbac/role/policybased/storage.go index c113e1d0f82..83f49e02dec 100644 --- a/pkg/registry/rbac/role/policybased/storage.go +++ b/pkg/registry/rbac/role/policybased/storage.go @@ -99,3 +99,13 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation, forceAllowCreate, options) } + +var _ rest.SingularNameProvider = &Storage{} + +func (s *Storage) GetSingularName() string { + snp, ok := s.StandardStorage.(rest.SingularNameProvider) + if !ok { + return "" + } + return snp.GetSingularName() +} diff --git a/pkg/registry/rbac/role/storage/storage.go b/pkg/registry/rbac/role/storage/storage.go index 6e6700ede98..373e1eb8fa6 100644 --- a/pkg/registry/rbac/role/storage/storage.go +++ b/pkg/registry/rbac/role/storage/storage.go @@ -33,9 +33,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against Role objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &rbac.Role{} }, - NewListFunc: func() runtime.Object { return &rbac.RoleList{} }, - DefaultQualifiedResource: rbac.Resource("roles"), + NewFunc: func() runtime.Object { return &rbac.Role{} }, + NewListFunc: func() runtime.Object { return &rbac.RoleList{} }, + DefaultQualifiedResource: rbac.Resource("roles"), + SingularQualifiedResource: rbac.Resource("role"), CreateStrategy: role.Strategy, UpdateStrategy: role.Strategy, diff --git a/pkg/registry/rbac/rolebinding/policybased/storage.go b/pkg/registry/rbac/rolebinding/policybased/storage.go index 64883635516..21171ec4022 100644 --- a/pkg/registry/rbac/rolebinding/policybased/storage.go +++ b/pkg/registry/rbac/rolebinding/policybased/storage.go @@ -142,3 +142,13 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation, forceAllowCreate, options) } + +var _ rest.SingularNameProvider = &Storage{} + +func (s *Storage) GetSingularName() string { + snp, ok := s.StandardStorage.(rest.SingularNameProvider) + if !ok { + return "" + } + return snp.GetSingularName() +} diff --git a/pkg/registry/rbac/rolebinding/storage/storage.go b/pkg/registry/rbac/rolebinding/storage/storage.go index 0cbcfa0f644..7e9f789d7de 100644 --- a/pkg/registry/rbac/rolebinding/storage/storage.go +++ b/pkg/registry/rbac/rolebinding/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against RoleBinding objects. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &rbac.RoleBinding{} }, - NewListFunc: func() runtime.Object { return &rbac.RoleBindingList{} }, - DefaultQualifiedResource: rbac.Resource("rolebindings"), + NewFunc: func() runtime.Object { return &rbac.RoleBinding{} }, + NewListFunc: func() runtime.Object { return &rbac.RoleBindingList{} }, + DefaultQualifiedResource: rbac.Resource("rolebindings"), + SingularQualifiedResource: rbac.Resource("rolebinding"), CreateStrategy: rolebinding.Strategy, UpdateStrategy: rolebinding.Strategy, diff --git a/pkg/registry/resource/podscheduling/storage/storage.go b/pkg/registry/resource/podscheduling/storage/storage.go index b977f283b85..5c066d2860e 100644 --- a/pkg/registry/resource/podscheduling/storage/storage.go +++ b/pkg/registry/resource/podscheduling/storage/storage.go @@ -40,10 +40,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against PodSchedulings. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &resource.PodScheduling{} }, - NewListFunc: func() runtime.Object { return &resource.PodSchedulingList{} }, - PredicateFunc: podscheduling.Match, - DefaultQualifiedResource: resource.Resource("podschedulings"), + NewFunc: func() runtime.Object { return &resource.PodScheduling{} }, + NewListFunc: func() runtime.Object { return &resource.PodSchedulingList{} }, + PredicateFunc: podscheduling.Match, + DefaultQualifiedResource: resource.Resource("podschedulings"), + SingularQualifiedResource: resource.Resource("podscheduling"), CreateStrategy: podscheduling.Strategy, UpdateStrategy: podscheduling.Strategy, diff --git a/pkg/registry/resource/resourceclaim/storage/storage.go b/pkg/registry/resource/resourceclaim/storage/storage.go index 959cb680bfe..5784c6e309c 100644 --- a/pkg/registry/resource/resourceclaim/storage/storage.go +++ b/pkg/registry/resource/resourceclaim/storage/storage.go @@ -40,10 +40,11 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ResourceClaims. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &resource.ResourceClaim{} }, - NewListFunc: func() runtime.Object { return &resource.ResourceClaimList{} }, - PredicateFunc: resourceclaim.Match, - DefaultQualifiedResource: resource.Resource("resourceclaims"), + NewFunc: func() runtime.Object { return &resource.ResourceClaim{} }, + NewListFunc: func() runtime.Object { return &resource.ResourceClaimList{} }, + PredicateFunc: resourceclaim.Match, + DefaultQualifiedResource: resource.Resource("resourceclaims"), + SingularQualifiedResource: resource.Resource("resourceclaim"), CreateStrategy: resourceclaim.Strategy, UpdateStrategy: resourceclaim.Strategy, diff --git a/pkg/registry/resource/resourceclaimtemplate/storage/storage.go b/pkg/registry/resource/resourceclaimtemplate/storage/storage.go index 282db91006d..f32851f9550 100644 --- a/pkg/registry/resource/resourceclaimtemplate/storage/storage.go +++ b/pkg/registry/resource/resourceclaimtemplate/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ResourceClass. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &resource.ResourceClaimTemplate{} }, - NewListFunc: func() runtime.Object { return &resource.ResourceClaimTemplateList{} }, - DefaultQualifiedResource: resource.Resource("resourceclaimtemplates"), + NewFunc: func() runtime.Object { return &resource.ResourceClaimTemplate{} }, + NewListFunc: func() runtime.Object { return &resource.ResourceClaimTemplateList{} }, + DefaultQualifiedResource: resource.Resource("resourceclaimtemplates"), + SingularQualifiedResource: resource.Resource("resourceclaimtemplate"), CreateStrategy: resourceclaimtemplate.Strategy, UpdateStrategy: resourceclaimtemplate.Strategy, diff --git a/pkg/registry/resource/resourceclass/storage/storage.go b/pkg/registry/resource/resourceclass/storage/storage.go index 310488ab04e..ad7d283b954 100644 --- a/pkg/registry/resource/resourceclass/storage/storage.go +++ b/pkg/registry/resource/resourceclass/storage/storage.go @@ -35,9 +35,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against ResourceClass. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &resource.ResourceClass{} }, - NewListFunc: func() runtime.Object { return &resource.ResourceClassList{} }, - DefaultQualifiedResource: resource.Resource("resourceclasses"), + NewFunc: func() runtime.Object { return &resource.ResourceClass{} }, + NewListFunc: func() runtime.Object { return &resource.ResourceClassList{} }, + DefaultQualifiedResource: resource.Resource("resourceclasses"), + SingularQualifiedResource: resource.Resource("resourceclass"), CreateStrategy: resourceclass.Strategy, UpdateStrategy: resourceclass.Strategy, diff --git a/pkg/registry/scheduling/priorityclass/storage/storage.go b/pkg/registry/scheduling/priorityclass/storage/storage.go index b84a573a12e..428ed730131 100644 --- a/pkg/registry/scheduling/priorityclass/storage/storage.go +++ b/pkg/registry/scheduling/priorityclass/storage/storage.go @@ -41,9 +41,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against priority classes. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &scheduling.PriorityClass{} }, - NewListFunc: func() runtime.Object { return &scheduling.PriorityClassList{} }, - DefaultQualifiedResource: scheduling.Resource("priorityclasses"), + NewFunc: func() runtime.Object { return &scheduling.PriorityClass{} }, + NewListFunc: func() runtime.Object { return &scheduling.PriorityClassList{} }, + DefaultQualifiedResource: scheduling.Resource("priorityclasses"), + SingularQualifiedResource: scheduling.Resource("priorityclass"), CreateStrategy: priorityclass.Strategy, UpdateStrategy: priorityclass.Strategy, diff --git a/pkg/registry/storage/csidriver/storage/storage.go b/pkg/registry/storage/csidriver/storage/storage.go index 3f3fea2774a..0444dc7bbd1 100644 --- a/pkg/registry/storage/csidriver/storage/storage.go +++ b/pkg/registry/storage/csidriver/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewStorage returns a RESTStorage object that will work against CSIDrivers func NewStorage(optsGetter generic.RESTOptionsGetter) (*CSIDriverStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &storageapi.CSIDriver{} }, - NewListFunc: func() runtime.Object { return &storageapi.CSIDriverList{} }, - DefaultQualifiedResource: storageapi.Resource("csidrivers"), + NewFunc: func() runtime.Object { return &storageapi.CSIDriver{} }, + NewListFunc: func() runtime.Object { return &storageapi.CSIDriverList{} }, + DefaultQualifiedResource: storageapi.Resource("csidrivers"), + SingularQualifiedResource: storageapi.Resource("csidriver"), CreateStrategy: csidriver.Strategy, UpdateStrategy: csidriver.Strategy, diff --git a/pkg/registry/storage/csinode/storage/storage.go b/pkg/registry/storage/csinode/storage/storage.go index 6fd562f1fa0..b16b0611d3a 100644 --- a/pkg/registry/storage/csinode/storage/storage.go +++ b/pkg/registry/storage/csinode/storage/storage.go @@ -40,9 +40,10 @@ type REST struct { // NewStorage returns a RESTStorage object that will work against CSINodes func NewStorage(optsGetter generic.RESTOptionsGetter) (*CSINodeStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &storageapi.CSINode{} }, - NewListFunc: func() runtime.Object { return &storageapi.CSINodeList{} }, - DefaultQualifiedResource: storageapi.Resource("csinodes"), + NewFunc: func() runtime.Object { return &storageapi.CSINode{} }, + NewListFunc: func() runtime.Object { return &storageapi.CSINodeList{} }, + DefaultQualifiedResource: storageapi.Resource("csinodes"), + SingularQualifiedResource: storageapi.Resource("csinode"), CreateStrategy: csinode.Strategy, UpdateStrategy: csinode.Strategy, diff --git a/pkg/registry/storage/csistoragecapacity/storage/storage.go b/pkg/registry/storage/csistoragecapacity/storage/storage.go index b638d96b0f0..39fb8809846 100644 --- a/pkg/registry/storage/csistoragecapacity/storage/storage.go +++ b/pkg/registry/storage/csistoragecapacity/storage/storage.go @@ -38,9 +38,10 @@ type REST struct { // NewStorage returns a RESTStorage object that will work against CSIStorageCapacity func NewStorage(optsGetter generic.RESTOptionsGetter) (*CSIStorageCapacityStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &storageapi.CSIStorageCapacity{} }, - NewListFunc: func() runtime.Object { return &storageapi.CSIStorageCapacityList{} }, - DefaultQualifiedResource: storageapi.Resource("csistoragecapacities"), + NewFunc: func() runtime.Object { return &storageapi.CSIStorageCapacity{} }, + NewListFunc: func() runtime.Object { return &storageapi.CSIStorageCapacityList{} }, + DefaultQualifiedResource: storageapi.Resource("csistoragecapacities"), + SingularQualifiedResource: storageapi.Resource("csistoragecapacity"), TableConvertor: rest.NewDefaultTableConvertor(storageapi.Resource("csistoragecapacities")), diff --git a/pkg/registry/storage/storageclass/storage/storage.go b/pkg/registry/storage/storageclass/storage/storage.go index fb4394e6641..0899299cd34 100644 --- a/pkg/registry/storage/storageclass/storage/storage.go +++ b/pkg/registry/storage/storageclass/storage/storage.go @@ -36,9 +36,10 @@ type REST struct { // NewREST returns a RESTStorage object that will work against storage classes. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &storageapi.StorageClass{} }, - NewListFunc: func() runtime.Object { return &storageapi.StorageClassList{} }, - DefaultQualifiedResource: storageapi.Resource("storageclasses"), + NewFunc: func() runtime.Object { return &storageapi.StorageClass{} }, + NewListFunc: func() runtime.Object { return &storageapi.StorageClassList{} }, + DefaultQualifiedResource: storageapi.Resource("storageclasses"), + SingularQualifiedResource: storageapi.Resource("storageclass"), CreateStrategy: storageclass.Strategy, UpdateStrategy: storageclass.Strategy, diff --git a/pkg/registry/storage/volumeattachment/storage/storage.go b/pkg/registry/storage/volumeattachment/storage/storage.go index 91c1c2a1142..1d213730a74 100644 --- a/pkg/registry/storage/volumeattachment/storage/storage.go +++ b/pkg/registry/storage/volumeattachment/storage/storage.go @@ -46,9 +46,10 @@ type REST struct { // NewStorage returns a RESTStorage object that will work against VolumeAttachments func NewStorage(optsGetter generic.RESTOptionsGetter) (*VolumeAttachmentStorage, error) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &storageapi.VolumeAttachment{} }, - NewListFunc: func() runtime.Object { return &storageapi.VolumeAttachmentList{} }, - DefaultQualifiedResource: storageapi.Resource("volumeattachments"), + NewFunc: func() runtime.Object { return &storageapi.VolumeAttachment{} }, + NewListFunc: func() runtime.Object { return &storageapi.VolumeAttachmentList{} }, + DefaultQualifiedResource: storageapi.Resource("volumeattachments"), + SingularQualifiedResource: storageapi.Resource("volumeattachment"), CreateStrategy: volumeattachment.Strategy, UpdateStrategy: volumeattachment.Strategy, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 86bfc46641c..d0ec43e27a9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -732,6 +732,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd parameterCodec := runtime.NewParameterCodec(parameterScheme) resource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural} + singularResource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Singular} kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind} equivalentResourceRegistry.RegisterKindFor(resource, "", kind) @@ -801,6 +802,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd storages[v.Name] = customresource.NewStorage( resource.GroupResource(), + singularResource.GroupResource(), kind, schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.ListKind}, customresource.NewStrategy( diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go index 55876b97175..8d8226d4686 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go @@ -41,7 +41,7 @@ type CustomResourceStorage struct { Scale *ScaleREST } -func NewStorage(resource schema.GroupResource, kind, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor, replicasPathMapping fieldmanager.ResourcePathMappings) CustomResourceStorage { +func NewStorage(resource schema.GroupResource, singularResource schema.GroupResource, kind, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor, replicasPathMapping fieldmanager.ResourcePathMappings) CustomResourceStorage { var storage CustomResourceStorage store := &genericregistry.Store{ NewFunc: func() runtime.Object { @@ -56,8 +56,9 @@ func NewStorage(resource schema.GroupResource, kind, listKind schema.GroupVersio ret.SetGroupVersionKind(listKind) return ret }, - PredicateFunc: strategy.MatchCustomResourceDefinitionStorage, - DefaultQualifiedResource: resource, + PredicateFunc: strategy.MatchCustomResourceDefinitionStorage, + DefaultQualifiedResource: resource, + SingularQualifiedResource: singularResource, CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go index 151f5e6c545..8e920c277a1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go @@ -93,6 +93,7 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcd3testi table, _ := tableconvertor.New(headers) storage := customresource.NewStorage( + groupResource, groupResource, kind, schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go index 2523076edf9..6300a08ce43 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go @@ -43,10 +43,11 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*RES strategy := NewStrategy(scheme) store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinition{} }, - NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinitionList{} }, - PredicateFunc: MatchCustomResourceDefinition, - DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"), + NewFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinition{} }, + NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinitionList{} }, + PredicateFunc: MatchCustomResourceDefinition, + DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"), + SingularQualifiedResource: apiextensions.Resource("customresourcedefinition"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index 9de67ad7a60..b803377631d 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/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/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go index b0af449f09b..2c4d7b95793 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go @@ -1080,6 +1080,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if categoriesProvider, ok := storage.(rest.CategoriesProvider); ok { apiResource.Categories = categoriesProvider.Categories() } + if !isSubresource { + 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/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go index 40bca49665f..6e8d291a3d9 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go +++ b/staging/src/k8s.io/apiserver/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/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go index f7af4ec89fc..28fe2877cfe 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go +++ b/staging/src/k8s.io/apiserver/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/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go index 6330ea8f531..b8d47ff4fe3 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go @@ -89,6 +89,12 @@ type CategoriesProvider interface { Categories() []string } +// 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 { + GetSingularName() string +} + // GroupVersionKindProvider is used to specify a particular GroupVersionKind to discovery. This is used for polymorphic endpoints // which generally point to foreign versions. Scale refers to Scale.v1beta1.extensions for instance. // This trumps KindProvider since it is capable of providing the information required. diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go index c6b07fc3fff..ccffc8276ce 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go +++ b/staging/src/k8s.io/apiserver/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", diff --git a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go index d20573ed368..42dddcd8315 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go @@ -41,10 +41,11 @@ type REST struct { func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST { strategy := apiservice.NewStrategy(scheme) store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &apiregistration.APIService{} }, - NewListFunc: func() runtime.Object { return &apiregistration.APIServiceList{} }, - PredicateFunc: apiservice.MatchAPIService, - DefaultQualifiedResource: apiregistration.Resource("apiservices"), + NewFunc: func() runtime.Object { return &apiregistration.APIService{} }, + NewListFunc: func() runtime.Object { return &apiregistration.APIServiceList{} }, + PredicateFunc: apiservice.MatchAPIService, + DefaultQualifiedResource: apiregistration.Resource("apiservices"), + SingularQualifiedResource: apiregistration.Resource("apiservice"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/etcd.go b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/etcd.go index 4a28fadc5a4..1f49c1b162a 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/etcd.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/etcd.go @@ -30,10 +30,11 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*reg strategy := NewStrategy(scheme) store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &wardle.Fischer{} }, - NewListFunc: func() runtime.Object { return &wardle.FischerList{} }, - PredicateFunc: MatchFischer, - DefaultQualifiedResource: wardle.Resource("fischers"), + NewFunc: func() runtime.Object { return &wardle.Fischer{} }, + NewListFunc: func() runtime.Object { return &wardle.FischerList{} }, + PredicateFunc: MatchFischer, + DefaultQualifiedResource: wardle.Resource("fischers"), + SingularQualifiedResource: wardle.Resource("fischer"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/etcd.go b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/etcd.go index 271e63cd96a..0179a9221a0 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/etcd.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/etcd.go @@ -30,10 +30,11 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*reg strategy := NewStrategy(scheme) store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &wardle.Flunder{} }, - NewListFunc: func() runtime.Object { return &wardle.FlunderList{} }, - PredicateFunc: MatchFlunder, - DefaultQualifiedResource: wardle.Resource("flunders"), + NewFunc: func() runtime.Object { return &wardle.Flunder{} }, + NewListFunc: func() runtime.Object { return &wardle.FlunderList{} }, + PredicateFunc: MatchFlunder, + DefaultQualifiedResource: wardle.Resource("flunders"), + SingularQualifiedResource: wardle.Resource("flunder"), CreateStrategy: strategy, UpdateStrategy: strategy, diff --git a/test/cmd/discovery.sh b/test/cmd/discovery.sh index 035cdc10e6b..4979f4356d1 100755 --- a/test/cmd/discovery.sh +++ b/test/cmd/discovery.sh @@ -55,7 +55,152 @@ run_assert_short_name_tests() { output_message=$(kubectl get --raw=/api/v1) ## test if a short name is exported during discovery - kube::test::if_has_string "${output_message}" '{"name":"configmaps","singularName":"","namespaced":true,"kind":"ConfigMap","verbs":\["create","delete","deletecollection","get","list","patch","update","watch"\],"shortNames":\["cm"\],"storageVersionHash":' + kube::test::if_has_string "${output_message}" '{"name":"configmaps","singularName":"configmap","namespaced":true,"kind":"ConfigMap","verbs":\["create","delete","deletecollection","get","list","patch","update","watch"\],"shortNames":\["cm"\],"storageVersionHash":' + + # check that there is no pod with the name test-crd-example + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + kubectl create -f - << __EOF__ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: examples.test.com +spec: + group: test.com + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + test: + type: string + names: + plural: examples + singular: example + shortNames: + - pod + kind: Example +__EOF__ + + # Test that we can list this new custom resource + kube::test::wait_object_assert customresourcedefinitions "{{range.items}}{{if eq ${id_field:?} \"examples.test.com\"}}{{$id_field}}:{{end}}{{end}}" 'examples.test.com:' + + kubectl create -f - << __EOF__ +apiVersion: test.com/v1 +kind: Example +metadata: + name: test-crd-example +spec: + test: test +__EOF__ + + # Test that we can list this new custom resource + kube::test::wait_object_assert examples "{{range.items}}{{${id_field:?}}}:{{end}}" 'test-crd-example:' + + output_message=$(kubectl get examples) + kube::test::if_has_string "${output_message}" "test-crd-example" + + # test that get pod returns v1/pod instead crd + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + # invalidate cache and assure that still correct resource is shown + kubectl api-resources + + # retest the above cases after invalidating cache + output_message=$(kubectl get examples) + kube::test::if_has_string "${output_message}" "test-crd-example" + + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + # Cleanup + kubectl delete examples/test-crd-example + kubectl delete customresourcedefinition examples.test.com + + set +o nounset + set +o errexit +} + +run_assert_singular_name_tests() { + set -o nounset + set -o errexit + + create_and_use_new_namespace + kube::log::status "Testing assert singular name" + + # check that there is no pod with the name test-crd-example + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + kubectl create -f - << __EOF__ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: examples.test.com +spec: + group: test.com + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + test: + type: string + names: + plural: examples + singular: pod + kind: Example +__EOF__ + + # Test that we can list this new custom resource + kube::test::wait_object_assert customresourcedefinitions "{{range.items}}{{if eq ${id_field:?} \"examples.test.com\"}}{{$id_field}}:{{end}}{{end}}" 'examples.test.com:' + + kubectl create -f - << __EOF__ +apiVersion: test.com/v1 +kind: Example +metadata: + name: test-crd-example +spec: + test: test +__EOF__ + + # Test that we can list this new custom resource + kube::test::wait_object_assert examples "{{range.items}}{{$id_field}}:{{end}}" 'test-crd-example:' + + output_message=$(kubectl get examples) + kube::test::if_has_string "${output_message}" "test-crd-example" + + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + # invalidate cache and assure that still correct resource is shown + kubectl api-resources + + output_message=$(kubectl get examples) + kube::test::if_has_string "${output_message}" "test-crd-example" + + output_message=$(kubectl get pod) + kube::test::if_has_not_string "${output_message}" "test-crd-example" + + # Cleanup + kubectl delete examples/test-crd-example + kubectl delete customresourcedefinition examples.test.com set +o nounset set +o errexit diff --git a/test/cmd/legacy-script.sh b/test/cmd/legacy-script.sh index 19935394481..7ac43358393 100755 --- a/test/cmd/legacy-script.sh +++ b/test/cmd/legacy-script.sh @@ -504,7 +504,17 @@ runTests() { # Assert short name # ######################### - record_command run_assert_short_name_tests + if kube::test::if_supports_resource "${customresourcedefinitions}" && kube::test::if_supports_resource "${pods}" && kube::test::if_supports_resource "${configmaps}" ; then + record_command run_assert_short_name_tests + fi + + ######################### + # Assert singular name # + ######################### + + if kube::test::if_supports_resource "${customresourcedefinitions}" && kube::test::if_supports_resource "${pods}" ; then + record_command run_assert_singular_name_tests + fi ######################### # Assert categories # diff --git a/test/integration/apiserver/discovery/discovery_test.go b/test/integration/apiserver/discovery/discovery_test.go index c6b7caaa477..b27dbecb2b4 100644 --- a/test/integration/apiserver/discovery/discovery_test.go +++ b/test/integration/apiserver/discovery/discovery_test.go @@ -690,6 +690,33 @@ func TestGroupPriorty(t *testing.T) { }) } +func TestSingularNames(t *testing.T) { + server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--runtime-config=api/all=true"}, framework.SharedEtcd()) + t.Cleanup(server.TearDownFn) + + kubeClientSet, err := kubernetes.NewForConfig(server.ClientConfig) + require.NoError(t, err) + + _, resources, err := kubeClientSet.Discovery().ServerGroupsAndResources() + require.NoError(t, err) + + for _, rr := range resources { + for _, r := range rr.APIResources { + if strings.Contains(r.Name, "/") { + continue + } + if r.SingularName == "" { + t.Errorf("missing singularName for resource %q in %q", r.Name, rr.GroupVersion) + continue + } + if r.SingularName != strings.ToLower(r.Kind) { + t.Errorf("expected singularName for resource %q in %q to be %q, got %q", r.Name, rr.GroupVersion, strings.ToLower(r.Kind), r.SingularName) + continue + } + } + } +} + func makeCRDSpec(group string, kind string, namespaced bool, versions []string, categories ...string) apiextensionsv1.CustomResourceDefinitionSpec { scope := apiextensionsv1.NamespaceScoped if !namespaced {