Change:
<!-- This form is for bug reports and feature requests ONLY! If you're looking for help check and the . --Is this a BUG REPORT or FEATURE REQUEST?: What happened: got an error when trying to use a fieldSelector when watching a custom resource What you expected to happen: no error How to reproduce it (as minimally and precisely as possible): a CRD and then a CR for it. to watch with a fieldSelector, such as Anything else we need to know?: The customresource implementation has a defined, and it looks like it should support at least a field selector for and , but I'm guessing we need a bit more in place to make this work for . Environment: Kubernetes version (use ): HEAD (commit ) /sig api-machinery I can try to submit a PR to fix once I finish my current work item, if nobody picks this up sooner.
/area third-party-resource
cc
This is a more general form of . I don't have immediate plans to work on either of them, so you're welcome to take it.
sounds good. Do you want me to close this as a dupe?
I think there's a useful distinction between the two issues. If we support only , that would be enough to but this one tracks the idea of supporting arbitrary field selectors. By the way, if I recall correctly, when I applied the hack mentioned in the other thread to skip the step for CRDs, I think I saw that actually arbitrary field selectors worked too (not just ). That would mean enabling this might be easy on a technical level, although we may need to think through whether this could tie our hands if we ever wanted to add versioning support for CRD in the future. This issue is a better home for that discussion than the other one.
This would help a lot with debugging in prow. Today, the only thing we can do is bubble up information from the spec of a prowjob as labels and use a label selector.
What about just having 's just return the passed in label and value without any "conversion" for now?
I don't think we'd want to do it for all Unstructured, since there's no guarantee all Unstructured objects are CRD objects. However, it should be pretty easy to arrange for ConvertFieldLabel to be a no-op for CRD objects, which makes sense currently because CRD only allows one version for a given resource. We just need to make sure this will not prevent us from doing more with CRD versioning in the future. My suspicion is that it should be fine, but I haven't investigated properly.
ok, I can put together a PR to make this change. any gut reactions?
PR:
I am sorry to comment on this closed thread. Currently, I am working on a CRD and needs to support arbitrary field selectors. I've tried to do it by learning from what you do for . But it always throws error: . So, I am wondering if I am doing something wrong or it only supports limited field selectors for CRD, which is stated . Here is my code snippet:
CRDs still only support namespace and name for field selectors
Any plan to support arbitrary field selector for CRDs?
Not currently

Discussion:
load( "@io_bazel_rules_go//go:def.bzl", "go_library", <ins> "go_test", </ins> ) go_library(

], tags = ["automanaged"], ) <ins> go_test( name = "go_default_test", srcs = ["customresource_handler_test.go"], library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"], ) </ins>

selfLinkPrefix = "/" + path.Join("apis", crd.Spec.Group, crd.Spec.Version, "namespaces") + "/" } <ins> clusterScoped := crd.Spec.Scope == apiextensions.ClusterScoped </ins> requestScope := handlers.RequestScope{ Namer: handlers.ContextBasedNaming{ GetContext: func(req *http.Request) apirequest.Context {

return ret }, SelfLinker:         meta.NewAccessor(), <del> ClusterScoped:      crd.Spec.Scope == apiextensions.ClusterScoped, </del> <ins> ClusterScoped:      clusterScoped, </ins> SelfLinkPathPrefix: selfLinkPrefix, }, ContextFunc: func(req *http.Request) apirequest.Context {

Serializer:     unstructuredNegotiatedSerializer{typer: typer, creator: creator}, ParameterCodec: parameterCodec, <del> Creater:         creator, Convertor:       unstructured.UnstructuredObjectConverter{}, </del> <ins> Creater: creator, Convertor: crdObjectConverter{ UnstructuredObjectConverter: unstructured.UnstructuredObjectConverter{}, clusterScoped:               clusterScoped, }, </ins> Defaulter:       unstructuredDefaulter{parameterScheme}, Copier:          UnstructuredCopier{}, Typer:           typer,

return ret, nil } <ins> // crdObjectConverter is a converter that supports field selectors for CRDs. type crdObjectConverter struct { unstructured.UnstructuredObjectConverter clusterScoped bool } func (c crdObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { // We currently only support metadata.namespace and metadata.name. switch { case label == "metadata.name": return label, value, nil case !c.clusterScoped && label == "metadata.namespace": return label, value, nil default: return "", "", fmt.Errorf("field label not supported: %s", label) } } </ins> func (c *crdHandler) updateCustomResourceDefinition(oldObj, _ interface{}) { oldCRD := oldObj.(*apiextensions.CustomResourceDefinition) glog.V(4).Infof("Updating customresourcedefinition %s", oldCRD.Name)

<ins> /* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package apiserver import ( "testing" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func TestConvertFieldLabel(t *testing.T) { tests := []struct { name          string clusterScoped bool label         string expectError   bool }{ { name:          "cluster scoped - name is ok", clusterScoped: true, label:         "metadata.name", }, { name:          "cluster scoped - namespace is not ok", clusterScoped: true, label:         "metadata.namespace", expectError:   true, }, { name:          "cluster scoped - other field is not ok", clusterScoped: true, label:         "some.other.field", expectError:   true, }, { name:  "namespace scoped - name is ok", label: "metadata.name", }, { name:  "namespace scoped - namespace is ok", label: "metadata.namespace", }, { name:        "namespace scoped - other field is not ok", label:       "some.other.field", expectError: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := crdObjectConverter{ UnstructuredObjectConverter: unstructured.UnstructuredObjectConverter{}, clusterScoped:               test.clusterScoped, } label, value, err := c.ConvertFieldLabel("", "", test.label, "value") if e, a := test.expectError, err != nil; e != a { t.Fatalf("err: expected %t, got %t", e, a) } if test.expectError { if e, a := "field label not supported: "+test.label, err.Error(); e != a { t.Errorf("err: expected %s, got %s", e, a) } return } if e, a := test.label, label; e != a { t.Errorf("label: expected %s, got %s", e, a) } if e, a := "value", value; e != a { t.Errorf("value: expected %s, got %s", e, a) } }) } } </ins>

ns := "not-the-default" testSimpleCRUD(t, ns, noxuDefinition, noxuVersionClient) <ins> testFieldSelector(t, ns, noxuDefinition, noxuVersionClient) </ins> } func TestClusterScopedCRUD(t *testing.T) {

ns := "" testSimpleCRUD(t, ns, noxuDefinition, noxuVersionClient) <ins> testFieldSelector(t, ns, noxuDefinition, noxuVersionClient) </ins> } func testSimpleCRUD(t *testing.T, ns string, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition, noxuVersionClient dynamic.Interface) {

} } <ins> func testFieldSelector(t *testing.T, ns string, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition, noxuVersionClient dynamic.Interface) { noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) initialList, err := noxuResourceClient.List(metav1.ListOptions{}) if err != nil { t.Fatal(err) } if e, a := 0, len(initialList.(*unstructured.UnstructuredList).Items); e != a { t.Errorf("expected %v, got %v", e, a) } initialListTypeMeta, err := meta.TypeAccessor(initialList) if err != nil { t.Fatal(err) } if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Version, initialListTypeMeta.GetAPIVersion(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := noxuDefinition.Spec.Names.ListKind, initialListTypeMeta.GetKind(); e != a { t.Errorf("expected %v, got %v", e, a) } initialListListMeta, err := meta.ListAccessor(initialList) if err != nil { t.Fatal(err) } noxuWatch, err := noxuResourceClient.Watch( metav1.ListOptions{ ResourceVersion: initialListListMeta.GetResourceVersion(), FieldSelector:   "metadata.name=foo", }, ) if err != nil { t.Fatal(err) } defer noxuWatch.Stop() _, err = instantiateCustomResource(t, testserver.NewNoxuInstance(ns, "bar"), noxuResourceClient, noxuDefinition) if err != nil { t.Fatalf("unable to create noxu Instance:%v", err) } createdNoxuInstanceFoo, err := instantiateCustomResource(t, testserver.NewNoxuInstance(ns, "foo"), noxuResourceClient, noxuDefinition) if err != nil { t.Fatalf("unable to create noxu Instance:%v", err) } select { case watchEvent := <-noxuWatch.ResultChan(): if e, a := watch.Added, watchEvent.Type; e != a { t.Errorf("expected %v, got %v", e, a) break } createdObjectMeta, err := meta.Accessor(watchEvent.Object) if err != nil { t.Fatal(err) } // it should have a UUID if len(createdObjectMeta.GetUID()) == 0 { t.Errorf("missing uuid: %#v", watchEvent.Object) } if e, a := ns, createdObjectMeta.GetNamespace(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "foo", createdObjectMeta.GetName(); e != a { t.Errorf("expected %v, got %v", e, a) } createdTypeMeta, err := meta.TypeAccessor(watchEvent.Object) if err != nil { t.Fatal(err) } if e, a := noxuDefinition.Spec.Group+"/"+noxuDefinition.Spec.Version, createdTypeMeta.GetAPIVersion(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := noxuDefinition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a { t.Errorf("expected %v, got %v", e, a) } case <-time.After(5 * time.Second): t.Errorf("missing watch event") } gottenNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{}) if err != nil { t.Fatal(err) } if e, a := createdNoxuInstanceFoo, gottenNoxuInstance; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } listWithItem, err := noxuResourceClient.List(metav1.ListOptions{FieldSelector: "metadata.name=foo"}) if err != nil { t.Fatal(err) } if e, a := 1, len(listWithItem.(*unstructured.UnstructuredList).Items); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := *createdNoxuInstanceFoo, listWithItem.(*unstructured.UnstructuredList).Items[0]; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } if err := noxuResourceClient.Delete("bar", nil); err != nil { t.Fatal(err) } if err := noxuResourceClient.Delete("foo", nil); err != nil { t.Fatal(err) } listWithoutItem, err := noxuResourceClient.List(metav1.ListOptions{}) if err != nil { t.Fatal(err) } if e, a := 0, len(listWithoutItem.(*unstructured.UnstructuredList).Items); e != a { t.Errorf("expected %v, got %v", e, a) } select { case watchEvent := <-noxuWatch.ResultChan(): if e, a := watch.Deleted, watchEvent.Type; e != a { t.Errorf("expected %v, got %v", e, a) break } deletedObjectMeta, err := meta.Accessor(watchEvent.Object) if err != nil { t.Fatal(err) } // it should have a UUID createdObjectMeta, err := meta.Accessor(createdNoxuInstanceFoo) if err != nil { t.Fatal(err) } if e, a := createdObjectMeta.GetUID(), deletedObjectMeta.GetUID(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := ns, createdObjectMeta.GetNamespace(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "foo", createdObjectMeta.GetName(); e != a { t.Errorf("expected %v, got %v", e, a) } case <-time.After(5 * time.Second): t.Errorf("missing watch event") } } </ins> func TestDiscovery(t *testing.T) { group := "mygroup.example.com" version := "v1beta1"