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 @@
"kind"
],
"properties": {
"categories": {
"description": "Categories is a list of grouped resources custom resources belong to (e.g. 'all')",
"type": "array",
"items": {
"type": "string"
}
},
"kind": {
"description": "Kind is the serialized kind of the resource. It is normally CamelCase and singular.",
"type": "string"
......@@ -49,6 +49,9 @@ type CustomResourceDefinitionNames struct {
Kind string
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>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
......
......@@ -88,6 +88,10 @@ message CustomResourceDefinitionNames {
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
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
......
......@@ -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 <kind>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
......
......@@ -179,6 +179,7 @@ func autoConvert_v1beta1_CustomResourceDefinitionNames_To_apiextensions_CustomRe
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Kind = in.Kind
out.ListKind = in.ListKind
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
return nil
}
......@@ -193,6 +194,7 @@ func autoConvert_apiextensions_CustomResourceDefinitionNames_To_v1beta1_CustomRe
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Kind = in.Kind
out.ListKind = in.ListKind
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
return nil
}
......
......@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Categories != nil {
in, out := &in.Categories, &out.Categories
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
......
......@@ -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
}
......
......@@ -112,6 +112,11 @@ func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinit
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Categories != nil {
in, out := &in.Categories, &out.Categories
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
......
......@@ -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 {
......
......@@ -459,7 +459,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
statusSpec,
scaleSpec,
),
r.restOptionsGetter,
r.restOptionsGetter, crd.Status.AcceptedNames.Categories,
)
selfLinkPrefix := ""
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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) {
......
......@@ -72,6 +72,7 @@ func NewNoxuCustomResourceDefinition(scope apiextensionsv1beta1.ResourceScope) *
Kind: "WishIHadChosenNoxu",
ShortNames: []string{"foo", "bar", "abc", "def"},
ListKind: "NoxuItemList",
Categories: []string{"all"},
},
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