diff --git a/pkg/genericapiserver/endpoints/apiserver_test.go b/pkg/genericapiserver/endpoints/apiserver_test.go index a4dff817f3e..417cdb053ca 100644 --- a/pkg/genericapiserver/endpoints/apiserver_test.go +++ b/pkg/genericapiserver/endpoints/apiserver_test.go @@ -36,6 +36,7 @@ import ( apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -117,20 +118,11 @@ func newMapper() *meta.DefaultRESTMapper { } func addGrouplessTypes() { - type ListOptions struct { - Object runtime.Object - metav1.TypeMeta `json:",inline"` - LabelSelector string `json:"labelSelector,omitempty"` - FieldSelector string `json:"fieldSelector,omitempty"` - Watch bool `json:"watch,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty"` - TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` - } api.Scheme.AddKnownTypes(grouplessGroupVersion, - &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &ListOptions{}, &metav1.ExportOptions{}, + &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, &metav1.ExportOptions{}, &v1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) api.Scheme.AddKnownTypes(grouplessInternalGroupVersion, - &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &api.ListOptions{}, &metav1.ExportOptions{}, + &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) } @@ -145,12 +137,12 @@ func addTestTypes() { TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` } api.Scheme.AddKnownTypes(testGroupVersion, - &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &ListOptions{}, &metav1.ExportOptions{}, + &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &v1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &SimpleXGSubresource{}) api.Scheme.AddKnownTypes(testGroupVersion, &v1.Pod{}) api.Scheme.AddKnownTypes(testInternalGroupVersion, - &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &api.ListOptions{}, &metav1.ExportOptions{}, + &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &SimpleXGSubresource{}) api.Scheme.AddKnownTypes(testInternalGroupVersion, &api.Pod{}) @@ -163,17 +155,8 @@ func addTestTypes() { } func addNewTestTypes() { - type ListOptions struct { - Object runtime.Object - metav1.TypeMeta `json:",inline"` - LabelSelector string `json:"labelSelector,omitempty"` - FieldSelector string `json:"fieldSelector,omitempty"` - Watch bool `json:"watch,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty"` - TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` - } api.Scheme.AddKnownTypes(newGroupVersion, - &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &ListOptions{}, &metav1.ExportOptions{}, + &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &api.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &v1.Pod{}, ) @@ -393,7 +376,7 @@ func (storage *SimpleRESTStorage) Export(ctx request.Context, name string, opts return obj, storage.errors["export"] } -func (storage *SimpleRESTStorage) List(ctx request.Context, options *api.ListOptions) (runtime.Object, error) { +func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { storage.checkContext(ctx) result := &genericapitesting.SimpleList{ Items: storage.list, @@ -509,7 +492,7 @@ func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objIn } // Implement ResourceWatcher. -func (storage *SimpleRESTStorage) Watch(ctx request.Context, options *api.ListOptions) (watch.Interface, error) { +func (storage *SimpleRESTStorage) Watch(ctx request.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { storage.lock.Lock() defer storage.lock.Unlock() storage.checkContext(ctx) diff --git a/pkg/genericapiserver/endpoints/groupversion.go b/pkg/genericapiserver/endpoints/groupversion.go index bee6f541e89..2d4d62b37d6 100644 --- a/pkg/genericapiserver/endpoints/groupversion.go +++ b/pkg/genericapiserver/endpoints/groupversion.go @@ -53,6 +53,10 @@ type APIGroupVersion struct { // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If // empty, defaults to GroupVersion. OptionsExternalVersion *schema.GroupVersion + // MetaGroupVersion defaults to "meta.k8s.io/v1" and is the scheme group version used to decode + // common API implementations like ListOptions. Future changes will allow this to vary by group + // version (for when the inevitable meta/v2 group emerges). + MetaGroupVersion *schema.GroupVersion Mapper meta.RESTMapper diff --git a/pkg/genericapiserver/endpoints/handlers/rest.go b/pkg/genericapiserver/endpoints/handlers/rest.go index f6b7b529e7d..f9a69f9303e 100644 --- a/pkg/genericapiserver/endpoints/handlers/rest.go +++ b/pkg/genericapiserver/endpoints/handlers/rest.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" @@ -87,6 +88,8 @@ type RequestScope struct { Resource schema.GroupVersionResource Kind schema.GroupVersionKind Subresource string + + MetaGroupVersion schema.GroupVersion } func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) { @@ -264,8 +267,8 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch ctx := scope.ContextFunc(req) ctx = request.WithNamespace(ctx, namespace) - opts := api.ListOptions{} - if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &opts); err != nil { + opts := metainternalversion.ListOptions{} + if err := metainternalversion.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } @@ -912,8 +915,8 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco } } - listOptions := api.ListOptions{} - if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &listOptions); err != nil { + listOptions := metainternalversion.ListOptions{} + if err := metainternalversion.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } diff --git a/pkg/genericapiserver/endpoints/installer.go b/pkg/genericapiserver/endpoints/installer.go index 9af985c3469..43416ae67e4 100644 --- a/pkg/genericapiserver/endpoints/installer.go +++ b/pkg/genericapiserver/endpoints/installer.go @@ -521,6 +521,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag Resource: a.group.GroupVersion.WithResource(resource), Subresource: subresource, Kind: fqKindToRegister, + + MetaGroupVersion: metav1.SchemeGroupVersion, + } + if a.group.MetaGroupVersion != nil { + reqScope.MetaGroupVersion = *a.group.MetaGroupVersion } for _, action := range actions { versionedObject := storageMeta.ProducesObject(action.Verb) diff --git a/pkg/genericapiserver/registry/generic/registry/store.go b/pkg/genericapiserver/registry/generic/registry/store.go index 54d2c5536c2..a4422347919 100644 --- a/pkg/genericapiserver/registry/generic/registry/store.go +++ b/pkg/genericapiserver/registry/generic/registry/store.go @@ -25,6 +25,7 @@ import ( kubeerr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -221,7 +222,7 @@ func (e *Store) NewList() runtime.Object { // List returns a list of items matching labels and field according to the // store's PredicateFunc. -func (e *Store) List(ctx genericapirequest.Context, options *api.ListOptions) (runtime.Object, error) { +func (e *Store) List(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { label := labels.Everything() if options != nil && options.LabelSelector != nil { label = options.LabelSelector @@ -244,10 +245,10 @@ func (e *Store) List(ctx genericapirequest.Context, options *api.ListOptions) (r // ListPredicate returns a list of all the items matching the given // SelectionPredicate. -func (e *Store) ListPredicate(ctx genericapirequest.Context, p storage.SelectionPredicate, options *api.ListOptions) (runtime.Object, error) { +func (e *Store) ListPredicate(ctx genericapirequest.Context, p storage.SelectionPredicate, options *metainternalversion.ListOptions) (runtime.Object, error) { if options == nil { // By default we should serve the request from etcd. - options = &api.ListOptions{ResourceVersion: ""} + options = &metainternalversion.ListOptions{ResourceVersion: ""} } list := e.NewListFunc() if name, ok := p.MatchesSingle(); ok { @@ -830,7 +831,7 @@ func (e *Store) Delete(ctx genericapirequest.Context, name string, options *api. // are removing all objects of a given type) with the current API (it's technically // possibly with storage API, but watch is not delivered correctly then). // It will be possible to fix it with v3 etcd API. -func (e *Store) DeleteCollection(ctx genericapirequest.Context, options *api.DeleteOptions, listOptions *api.ListOptions) (runtime.Object, error) { +func (e *Store) DeleteCollection(ctx genericapirequest.Context, options *api.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { listObj, err := e.List(ctx, listOptions) if err != nil { return nil, err @@ -921,7 +922,7 @@ func (e *Store) finalizeDelete(obj runtime.Object, runHooks bool) (runtime.Objec // WatchPredicate. If possible, you should customize PredicateFunc to produce // a matcher that matches by key. SelectionPredicate does this for you // automatically. -func (e *Store) Watch(ctx genericapirequest.Context, options *api.ListOptions) (watch.Interface, error) { +func (e *Store) Watch(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { label := labels.Everything() if options != nil && options.LabelSelector != nil { label = options.LabelSelector diff --git a/pkg/genericapiserver/registry/generic/registry/store_test.go b/pkg/genericapiserver/registry/generic/registry/store_test.go index c1e4c418d9c..0e42a2f8e66 100644 --- a/pkg/genericapiserver/registry/generic/registry/store_test.go +++ b/pkg/genericapiserver/registry/generic/registry/store_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -239,7 +240,7 @@ func TestStoreListResourceVersion(t *testing.T) { waitListCh := make(chan runtime.Object, 1) go func(listRev uint64) { - option := &api.ListOptions{ResourceVersion: strconv.FormatUint(listRev, 10)} + option := &metainternalversion.ListOptions{ResourceVersion: strconv.FormatUint(listRev, 10)} // It will wait until we create the second pod. l, err := registry.List(ctx, option) if err != nil { @@ -1078,7 +1079,7 @@ func TestStoreDeleteCollection(t *testing.T) { } // Delete all pods. - deleted, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) + deleted, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1119,7 +1120,7 @@ func TestStoreDeleteCollectionNotFound(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) + _, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1157,7 +1158,7 @@ func TestStoreDeleteCollectionWithWatch(t *testing.T) { } defer watcher.Stop() - if _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}); err != nil { + if _, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}); err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/genericapiserver/registry/rest/rest.go b/pkg/genericapiserver/registry/rest/rest.go index 09e3f8a337f..bab08ae4ece 100644 --- a/pkg/genericapiserver/registry/rest/rest.go +++ b/pkg/genericapiserver/registry/rest/rest.go @@ -21,6 +21,7 @@ import ( "net/http" "net/url" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -71,7 +72,7 @@ type Lister interface { // This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object) NewList() runtime.Object // List selects resources in the storage which match to the selector. 'options' can be nil. - List(ctx genericapirequest.Context, options *api.ListOptions) (runtime.Object, error) + List(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (runtime.Object, error) } // Exporter is an object that knows how to strip a RESTful resource for export @@ -152,7 +153,7 @@ type CollectionDeleter interface { // them or return an invalid request error. // DeleteCollection may not be atomic - i.e. it may delete some objects and still // return an error after it. On success, returns a list of deleted objects. - DeleteCollection(ctx genericapirequest.Context, options *api.DeleteOptions, listOptions *api.ListOptions) (runtime.Object, error) + DeleteCollection(ctx genericapirequest.Context, options *api.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) } // Creater is an object that can create an instance of a RESTful object. @@ -225,7 +226,7 @@ type Watcher interface { // are supported; an error should be returned if 'field' tries to select on a field that // isn't supported. 'resourceVersion' allows for continuing/starting a watch at a // particular version. - Watch(ctx genericapirequest.Context, options *api.ListOptions) (watch.Interface, error) + Watch(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) } // StandardStorage is an interface covering the common verbs. Provided for testing whether a diff --git a/pkg/genericapiserver/registry/rest/resttest/resttest.go b/pkg/genericapiserver/registry/rest/resttest/resttest.go index 38eaddf6fac..3b254d7a612 100644 --- a/pkg/genericapiserver/registry/rest/resttest/resttest.go +++ b/pkg/genericapiserver/registry/rest/resttest/resttest.go @@ -24,6 +24,7 @@ import ( "time" "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/fields" @@ -1196,7 +1197,7 @@ func (t *Tester) testListMatchLabels(obj runtime.Object, assignFn AssignFunc) { filtered := []runtime.Object{objs[1]} selector := labels.SelectorFromSet(labels.Set(testLabels)) - options := &api.ListOptions{LabelSelector: selector} + options := &metainternalversion.ListOptions{LabelSelector: selector} listObj, err := t.storage.(rest.Lister).List(ctx, options) if err != nil { t.Errorf("unexpected error: %v", err) @@ -1238,7 +1239,7 @@ func (t *Tester) testWatchFields(obj runtime.Object, emitFn EmitFunc, fieldsPass for _, field := range fieldsPass { for _, action := range actions { - options := &api.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} + options := &metainternalversion.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) if err != nil { t.Errorf("unexpected error: %v, %v", err, action) @@ -1262,7 +1263,7 @@ func (t *Tester) testWatchFields(obj runtime.Object, emitFn EmitFunc, fieldsPass for _, field := range fieldsFail { for _, action := range actions { - options := &api.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} + options := &metainternalversion.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) if err != nil { t.Errorf("unexpected error: %v", err) @@ -1287,7 +1288,7 @@ func (t *Tester) testWatchLabels(obj runtime.Object, emitFn EmitFunc, labelsPass for _, label := range labelsPass { for _, action := range actions { - options := &api.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} + options := &metainternalversion.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) if err != nil { t.Errorf("unexpected error: %v", err) @@ -1310,7 +1311,7 @@ func (t *Tester) testWatchLabels(obj runtime.Object, emitFn EmitFunc, labelsPass for _, label := range labelsFail { for _, action := range actions { - options := &api.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} + options := &metainternalversion.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) if err != nil { t.Errorf("unexpected error: %v", err) diff --git a/pkg/genericapiserver/server/genericapiserver.go b/pkg/genericapiserver/server/genericapiserver.go index f3edaba858c..b9ff0974a2d 100644 --- a/pkg/genericapiserver/server/genericapiserver.go +++ b/pkg/genericapiserver/server/genericapiserver.go @@ -59,6 +59,10 @@ type APIGroupInfo struct { // If nil, defaults to groupMeta.GroupVersion. // TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed. OptionsExternalVersion *schema.GroupVersion + // MetaGroupVersion defaults to "meta.k8s.io/v1" and is the scheme group version used to decode + // common API implementations like ListOptions. Future changes will allow this to vary by group + // version (for when the inevitable meta/v2 group emerges). + MetaGroupVersion *schema.GroupVersion // Scheme includes all of the types used by this group and how to convert between them (or // to convert objects from outside of this group that are accepted in this API). @@ -322,7 +326,8 @@ func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion) *genericapi.APIGroupVersion { return &genericapi.APIGroupVersion{ - GroupVersion: groupVersion, + GroupVersion: groupVersion, + MetaGroupVersion: apiGroupInfo.MetaGroupVersion, ParameterCodec: apiGroupInfo.ParameterCodec, Serializer: apiGroupInfo.NegotiatedSerializer,