Unverified Commit 8d406bf4 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #59561 from nikhita/customresources-kubectl-get-all

Automatic merge from submit-queue (batch tested with PRs 60302, 57921, 59042, 60126, 59561). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Allow to specify categories for custom resources Allow to specify categories for custom resources so that we can get it working with `kubectl get all`. Adds a new field `Categories` in the CRD spec. **Release note**: ```release-note Custom resources can be listed with a set of grouped resources (category) by specifying the categories in the CustomResourceDefinition spec. Example: They can be used with `kubectl get all`, where `all` is a category. ``` /cc sttts liggitt deads2k
parents 5b0383ed 7ac2b3c8
...@@ -83954,6 +83954,13 @@ ...@@ -83954,6 +83954,13 @@
"kind" "kind"
], ],
"properties": { "properties": {
"categories": {
"description": "Categories is a list of grouped resources custom resources belong to (e.g. 'all')",
"type": "array",
"items": {
"type": "string"
}
},
"kind": { "kind": {
"description": "Kind is the serialized kind of the resource. It is normally CamelCase and singular.", "description": "Kind is the serialized kind of the resource. It is normally CamelCase and singular.",
"type": "string" "type": "string"
...@@ -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
......
...@@ -88,6 +88,10 @@ message CustomResourceDefinitionNames { ...@@ -88,6 +88,10 @@ message CustomResourceDefinitionNames {
// 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.
optional string listKind = 5; optional string listKind = 5;
// Categories is a list of grouped resources custom resources belong to (e.g. 'all')
// +optional
repeated string categories = 6;
} }
// CustomResourceDefinitionSpec describes how a user wants their resource to appear // CustomResourceDefinitionSpec describes how a user wants their resource to appear
......
...@@ -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
......
...@@ -179,6 +179,7 @@ func autoConvert_v1beta1_CustomResourceDefinitionNames_To_apiextensions_CustomRe ...@@ -179,6 +179,7 @@ func autoConvert_v1beta1_CustomResourceDefinitionNames_To_apiextensions_CustomRe
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames)) out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Kind = in.Kind out.Kind = in.Kind
out.ListKind = in.ListKind out.ListKind = in.ListKind
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
return nil return nil
} }
...@@ -193,6 +194,7 @@ func autoConvert_apiextensions_CustomResourceDefinitionNames_To_v1beta1_CustomRe ...@@ -193,6 +194,7 @@ func autoConvert_apiextensions_CustomResourceDefinitionNames_To_v1beta1_CustomRe
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames)) out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Kind = in.Kind out.Kind = in.Kind
out.ListKind = in.ListKind out.ListKind = in.ListKind
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
return nil return nil
} }
......
...@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit ...@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Categories != nil {
in, out := &in.Categories, &out.Categories
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -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
} }
......
...@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit ...@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Categories != nil {
in, out := &in.Categories, &out.Categories
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -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