From e7341f4debfa6432ae1ece98047bbcfd1763c3d2 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Thu, 8 Feb 2018 08:18:46 +0530 Subject: [PATCH] Add Categories to CRD spec We can group custom resources into categories i.e. use them with kubectl get all. --- .../pkg/apis/apiextensions/types.go | 3 +++ .../pkg/apis/apiextensions/v1beta1/types.go | 3 +++ .../apis/apiextensions/validation/validation.go | 7 ++++++- .../customresource_discovery_controller.go | 1 + .../pkg/apiserver/customresource_handler.go | 2 +- .../pkg/controller/status/naming_controller.go | 2 ++ .../pkg/registry/customresource/etcd.go | 17 +++++++++++++---- .../pkg/registry/customresource/etcd_test.go | 16 +++++++++++++++- .../test/integration/basic_test.go | 4 ++++ .../test/integration/testserver/resources.go | 1 + 10 files changed, 49 insertions(+), 7 deletions(-) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go index b7a20d9188f..0deb7cbd081 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go @@ -49,6 +49,9 @@ type CustomResourceDefinitionNames struct { Kind string // ListKind is the serialized kind of the list for this resource. Defaults to List. ListKind string + // Categories is a list of grouped resources custom resources belong to (e.g. 'all') + // +optional + Categories []string } // ResourceScope is an enum defining the different scopes available to a custom resource diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go index 1afa457df21..3a4da9aea8a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go @@ -53,6 +53,9 @@ type CustomResourceDefinitionNames struct { Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"` // ListKind is the serialized kind of the list for this resource. Defaults to List. ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"` + // Categories is a list of grouped resources custom resources belong to (e.g. 'all') + // +optional + Categories []string `json:"categories,omitempty" protobuf:"bytes,6,rep,name=categories"` } // ResourceScope is an enum defining the different scopes available to a custom resource diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index afec55cda9e..be4a0e34f46 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -165,7 +165,6 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe if errs := validationutil.IsDNS1035Label(shortName); len(errs) > 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("shortNames").Index(i), shortName, strings.Join(errs, ","))) } - } // kind and listKind may not be the same or parsing become ambiguous @@ -173,6 +172,12 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe allErrs = append(allErrs, field.Invalid(fldPath.Child("listKind"), names.ListKind, "kind and listKind may not be the same")) } + for i, category := range names.Categories { + if errs := validationutil.IsDNS1035Label(category); len(errs) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("categories").Index(i), category, strings.Join(errs, ","))) + } + } + return allErrs } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go index c794517401d..b180e105b4c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go @@ -117,6 +117,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { Kind: crd.Status.AcceptedNames.Kind, Verbs: verbs, ShortNames: crd.Status.AcceptedNames.ShortNames, + Categories: crd.Status.AcceptedNames.Categories, }) if crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil { 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 dcc185c5f9b..40c58e04210 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 @@ -459,7 +459,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource statusSpec, scaleSpec, ), - r.restOptionsGetter, + r.restOptionsGetter, crd.Status.AcceptedNames.Categories, ) selfLinkPrefix := "" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go index c0a7cc86f9d..16016e493a5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go @@ -182,6 +182,8 @@ func (c *NamingConditionController) calculateNamesAndConditions(in *apiextension newNames.ListKind = requestedNames.ListKind } + newNames.Categories = requestedNames.Categories + // if we haven't changed the condition, then our names must be good. if namesAcceptedCondition.Status == apiextensions.ConditionUnknown { namesAcceptedCondition.Status = apiextensions.ConditionTrue 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 aef806a7796..305cee3b664 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 @@ -39,8 +39,8 @@ type CustomResourceStorage struct { Scale *ScaleREST } -func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter) CustomResourceStorage { - customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter) +func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) CustomResourceStorage { + customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories) customResourceRegistry := NewRegistry(customResourceREST) s := CustomResourceStorage{ @@ -71,10 +71,11 @@ func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, // REST implements a RESTStorage for API services against etcd type REST struct { *genericregistry.Store + categories []string } // newREST returns a RESTStorage object that will work against API services. -func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { +func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) (*REST, *StatusREST) { store := &genericregistry.Store{ NewFunc: func() runtime.Object { return &unstructured.Unstructured{} }, NewListFunc: func() runtime.Object { @@ -97,7 +98,15 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st statusStore := *store statusStore.UpdateStrategy = NewStatusStrategy(strategy) - return &REST{store}, &StatusREST{store: &statusStore} + return &REST{store, categories}, &StatusREST{store: &statusStore} +} + +// Implement CategoriesProvider +var _ rest.CategoriesProvider = &REST{} + +// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. +func (r *REST) Categories() []string { + return r.categories } // StatusREST implements the REST endpoint for changing the status of a CustomResource 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 6b4bb02e668..f5502854512 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 @@ -18,6 +18,7 @@ package customresource_test import ( "io" + "reflect" "strings" "testing" @@ -82,7 +83,7 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin status, scale, ), - restOptions, + restOptions, []string{"all"}, ) return storage, server @@ -153,6 +154,19 @@ func TestDelete(t *testing.T) { test.TestDelete(validNewCustomResource()) } +func TestCategories(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + + expected := []string{"all"} + actual := storage.CustomResource.Categories() + ok := reflect.DeepEqual(actual, expected) + if !ok { + t.Errorf("categories are not equal. expected = %v actual = %v", expected, actual) + } +} + func TestStatusUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go index 2df2076bf0f..e373a178a28 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go @@ -388,6 +388,10 @@ func TestDiscovery(t *testing.T) { if !reflect.DeepEqual([]string(r.Verbs), expectedVerbs) { t.Fatalf("Unexpected verbs for resource \"noxus\" in group version %v/%v via discovery: expected=%v got=%v", group, version, expectedVerbs, r.Verbs) } + + if !reflect.DeepEqual(r.Categories, []string{"all"}) { + t.Fatalf("Expected exactly the category \"all\" in group version %v/%v via discovery, got: %v", group, version, r.Categories) + } } func TestNoNamespaceReject(t *testing.T) { diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go index 9398afd9725..bf54e149086 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go @@ -72,6 +72,7 @@ func NewNoxuCustomResourceDefinition(scope apiextensionsv1beta1.ResourceScope) * Kind: "WishIHadChosenNoxu", ShortNames: []string{"foo", "bar", "abc", "def"}, ListKind: "NoxuItemList", + Categories: []string{"all"}, }, Scope: scope, },