Change:
Follow a TPR an instance a single instance return 400. (watch a collection of TPR works) master and kubectl version: 1.7.1 CRD has the same issue. /assign
There are no sig labels on this issue. Please by: a sig: e.g., to notify the contributor experience sig, OR the label manually: e.g., to apply the label Note: Method 1 will trigger an email to the group. You can find the group list and label list . The in the method 1 has to be replaced with one of these: bugs, feature-requests, pr-reviews, test-failures, proposals
I was able to reproduce this with CRD on master. The problem is that kubectl implements a watch on a single object as a watch on the collection with a field selector that filters by . However, field selectors don't work on CRD objects because they are stored as , which does not support : This method is used to transform the field selector from versioned to internal field names: As a test, I bypassed that check for Unstructured objects, and watches on single CRD objects started working. Given that CRD doesn't support multiple versions anyway, it seems feasible to support this with a properly-targeted override of specific to CRD objects. cc
If we could support name and namespace generally on all objects containing object meta, I think I'm in favor of the concept.
I think it's feasible to fix this for CRD at some point. However, given that TPR is already gone in master, I don't think it's worth fixing for TPR unless it's somehow a regression in 1.7. Do you have any objections to that?
Having CRD support is good enough for me and service catalog. Will you backport the fix for CRD to 1.7?
At this stage I wouldn't expect that since it isn't a regression (I don't think) for either extension mechanism.
cc
I am also getting the same error when i try to watch CRDs and filter on a specific field. Im a currently using the to watch for CRDs. On a related note, i noticed that the takes a . Is there a way to pass a label selector so that i can watch CRDs using labels?
Also is this issue slated for 1.8? (For CRDs that is)
The general issue of supporting field selectors for CRD is tracked in . I don't think it's slated for 1.8. The methods on should take a , which can contain an optional .

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"