Commit e7341f4d authored by Nikhita Raghunath's avatar Nikhita Raghunath

Add Categories to CRD spec

We can group custom resources into categories i.e. use them with kubectl get all.
parent da564ef4
...@@ -49,6 +49,9 @@ type CustomResourceDefinitionNames struct { ...@@ -49,6 +49,9 @@ type CustomResourceDefinitionNames struct {
Kind string Kind string
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List. // ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
ListKind string 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 // ResourceScope is an enum defining the different scopes available to a custom resource
......
...@@ -53,6 +53,9 @@ type CustomResourceDefinitionNames struct { ...@@ -53,6 +53,9 @@ type CustomResourceDefinitionNames struct {
Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"` Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"`
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List. // ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"` 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 // ResourceScope is an enum defining the different scopes available to a custom resource
......
...@@ -165,7 +165,6 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe ...@@ -165,7 +165,6 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe
if errs := validationutil.IsDNS1035Label(shortName); len(errs) > 0 { if errs := validationutil.IsDNS1035Label(shortName); len(errs) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("shortNames").Index(i), shortName, strings.Join(errs, ","))) 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 // kind and listKind may not be the same or parsing become ambiguous
...@@ -173,6 +172,12 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe ...@@ -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")) 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 return allErrs
} }
......
...@@ -117,6 +117,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { ...@@ -117,6 +117,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
Kind: crd.Status.AcceptedNames.Kind, Kind: crd.Status.AcceptedNames.Kind,
Verbs: verbs, Verbs: verbs,
ShortNames: crd.Status.AcceptedNames.ShortNames, ShortNames: crd.Status.AcceptedNames.ShortNames,
Categories: crd.Status.AcceptedNames.Categories,
}) })
if crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil { if crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil {
......
...@@ -459,7 +459,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -459,7 +459,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
statusSpec, statusSpec,
scaleSpec, scaleSpec,
), ),
r.restOptionsGetter, r.restOptionsGetter, crd.Status.AcceptedNames.Categories,
) )
selfLinkPrefix := "" selfLinkPrefix := ""
......
...@@ -182,6 +182,8 @@ func (c *NamingConditionController) calculateNamesAndConditions(in *apiextension ...@@ -182,6 +182,8 @@ func (c *NamingConditionController) calculateNamesAndConditions(in *apiextension
newNames.ListKind = requestedNames.ListKind newNames.ListKind = requestedNames.ListKind
} }
newNames.Categories = requestedNames.Categories
// if we haven't changed the condition, then our names must be good. // if we haven't changed the condition, then our names must be good.
if namesAcceptedCondition.Status == apiextensions.ConditionUnknown { if namesAcceptedCondition.Status == apiextensions.ConditionUnknown {
namesAcceptedCondition.Status = apiextensions.ConditionTrue namesAcceptedCondition.Status = apiextensions.ConditionTrue
......
...@@ -39,8 +39,8 @@ type CustomResourceStorage struct { ...@@ -39,8 +39,8 @@ type CustomResourceStorage struct {
Scale *ScaleREST Scale *ScaleREST
} }
func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter) CustomResourceStorage { func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) CustomResourceStorage {
customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter) customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories)
customResourceRegistry := NewRegistry(customResourceREST) customResourceRegistry := NewRegistry(customResourceREST)
s := CustomResourceStorage{ s := CustomResourceStorage{
...@@ -71,10 +71,11 @@ func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, ...@@ -71,10 +71,11 @@ func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind,
// REST implements a RESTStorage for API services against etcd // REST implements a RESTStorage for API services against etcd
type REST struct { type REST struct {
*genericregistry.Store *genericregistry.Store
categories []string
} }
// newREST returns a RESTStorage object that will work against API services. // 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{ store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &unstructured.Unstructured{} }, NewFunc: func() runtime.Object { return &unstructured.Unstructured{} },
NewListFunc: func() runtime.Object { NewListFunc: func() runtime.Object {
...@@ -97,7 +98,15 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st ...@@ -97,7 +98,15 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st
statusStore := *store statusStore := *store
statusStore.UpdateStrategy = NewStatusStrategy(strategy) 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 // StatusREST implements the REST endpoint for changing the status of a CustomResource
......
...@@ -18,6 +18,7 @@ package customresource_test ...@@ -18,6 +18,7 @@ package customresource_test
import ( import (
"io" "io"
"reflect"
"strings" "strings"
"testing" "testing"
...@@ -82,7 +83,7 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin ...@@ -82,7 +83,7 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin
status, status,
scale, scale,
), ),
restOptions, restOptions, []string{"all"},
) )
return storage, server return storage, server
...@@ -153,6 +154,19 @@ func TestDelete(t *testing.T) { ...@@ -153,6 +154,19 @@ func TestDelete(t *testing.T) {
test.TestDelete(validNewCustomResource()) 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) { func TestStatusUpdate(t *testing.T) {
storage, server := newStorage(t) storage, server := newStorage(t)
defer server.Terminate(t) defer server.Terminate(t)
......
...@@ -388,6 +388,10 @@ func TestDiscovery(t *testing.T) { ...@@ -388,6 +388,10 @@ func TestDiscovery(t *testing.T) {
if !reflect.DeepEqual([]string(r.Verbs), expectedVerbs) { 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) 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) { func TestNoNamespaceReject(t *testing.T) {
......
...@@ -72,6 +72,7 @@ func NewNoxuCustomResourceDefinition(scope apiextensionsv1beta1.ResourceScope) * ...@@ -72,6 +72,7 @@ func NewNoxuCustomResourceDefinition(scope apiextensionsv1beta1.ResourceScope) *
Kind: "WishIHadChosenNoxu", Kind: "WishIHadChosenNoxu",
ShortNames: []string{"foo", "bar", "abc", "def"}, ShortNames: []string{"foo", "bar", "abc", "def"},
ListKind: "NoxuItemList", ListKind: "NoxuItemList",
Categories: []string{"all"},
}, },
Scope: scope, Scope: scope,
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment