From 62993a9d99d7c6b46bdbca895afe01a976debbbe Mon Sep 17 00:00:00 2001 From: Simon Pasquier Date: Wed, 14 Feb 2024 03:52:25 +0100 Subject: [PATCH 1/2] fix: don't fail metadata transform on unknown types (#6298) * fix: don't fail metadata transform on unknown types This change modifies the `PartialObjectMetadataStrip` function to return the object unmodified if casting to `*v1.PartialObjectMetadata` fails. When the informer processes a deleted object, its type can be `cache.DeletedFinalStateUnknown`. Co-authored-by: Ayoub Mrini Signed-off-by: Simon Pasquier * test: add TestPartialObjectMetadataStripOnDeletedFinalStateUnknown Co-authored-by: machine424 Signed-off-by: Simon Pasquier Signed-off-by: machine424 --------- Signed-off-by: Simon Pasquier Signed-off-by: machine424 Co-authored-by: Ayoub Mrini Co-authored-by: machine424 --- pkg/informers/informers.go | 10 ++- pkg/informers/informers_test.go | 150 +++++++++++++++++++++++++++----- 2 files changed, 138 insertions(+), 22 deletions(-) diff --git a/pkg/informers/informers.go b/pkg/informers/informers.go index 80b8dd23e0..a44a08dd7e 100644 --- a/pkg/informers/informers.go +++ b/pkg/informers/informers.go @@ -89,10 +89,18 @@ func NewInformersForResourceWithTransform(ifs FactoriesForNamespaces, resource s // * ManagedFields // * Finalizers // * OwnerReferences. +// +// If the passed object isn't of type *v1.PartialObjectMetadata, it is returned unmodified. +// +// It matches the cache.TransformFunc type and can be used by informers +// watching PartialObjectMetadata objects to reduce memory consumption. +// See https://pkg.go.dev/k8s.io/client-go@v0.29.1/tools/cache#TransformFunc for details. func PartialObjectMetadataStrip(obj interface{}) (interface{}, error) { partialMeta, ok := obj.(*v1.PartialObjectMetadata) if !ok { - return nil, fmt.Errorf("internal error: cannot cast object %#+v to PartialObjectMetadata", obj) + // Don't do anything if the cast isn't successful. + // The object might be of type "cache.DeletedFinalStateUnknown". + return obj, nil } partialMeta.Annotations = nil diff --git a/pkg/informers/informers_test.go b/pkg/informers/informers_test.go index efd6330df7..edb32cd7a7 100644 --- a/pkg/informers/informers_test.go +++ b/pkg/informers/informers_test.go @@ -15,18 +15,25 @@ package informers import ( + "context" "reflect" "sort" "strings" + "sync/atomic" "testing" + "time" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/metadata/fake" + kubetesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -46,7 +53,7 @@ func (m *mockFactory) Get(name string) (runtime.Object, error) { return obj, nil } - return nil, errors.NewNotFound(schema.GroupResource{}, name) + return nil, apierrors.NewNotFound(schema.GroupResource{}, name) } func (m *mockFactory) ByNamespace(_ string) cache.GenericNamespaceLister { @@ -96,7 +103,7 @@ func TestInformers(t *testing.T) { } _, err = ifs.Get("bar") - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { t.Errorf("expected IsNotFound error, got %v", err) return } @@ -107,14 +114,14 @@ func TestNewInformerOptions(t *testing.T) { for _, tc := range []struct { name string allowedNamespaces, deniedNamespaces map[string]struct{} - tweaks func(*v1.ListOptions) + tweaks func(*metav1.ListOptions) - expectedOptions v1.ListOptions + expectedOptions metav1.ListOptions expectedNamespaces []string }{ { name: "all unset", - expectedOptions: v1.ListOptions{}, + expectedOptions: metav1.ListOptions{}, expectedNamespaces: nil, }, { @@ -123,7 +130,7 @@ func TestNewInformerOptions(t *testing.T) { "foo": {}, "bar": {}, }, - expectedOptions: v1.ListOptions{}, + expectedOptions: metav1.ListOptions{}, expectedNamespaces: []string{ "foo", "bar", @@ -135,11 +142,11 @@ func TestNewInformerOptions(t *testing.T) { "foo": {}, "bar": {}, }, - tweaks: func(options *v1.ListOptions) { + tweaks: func(options *metav1.ListOptions) { options.FieldSelector = "metadata.name=foo" }, - expectedOptions: v1.ListOptions{ + expectedOptions: metav1.ListOptions{ FieldSelector: "metadata.name=foo", }, expectedNamespaces: []string{ @@ -158,7 +165,7 @@ func TestNewInformerOptions(t *testing.T) { "denied2": {}, }, - expectedOptions: v1.ListOptions{}, + expectedOptions: metav1.ListOptions{}, expectedNamespaces: []string{ "foo", "bar", @@ -174,7 +181,7 @@ func TestNewInformerOptions(t *testing.T) { "denied2": {}, }, - expectedOptions: v1.ListOptions{}, + expectedOptions: metav1.ListOptions{}, expectedNamespaces: []string{ "foo", }, @@ -182,7 +189,7 @@ func TestNewInformerOptions(t *testing.T) { { name: "all allowed namespaces denying namespaces", allowedNamespaces: map[string]struct{}{ - v1.NamespaceAll: {}, + metav1.NamespaceAll: {}, }, deniedNamespaces: map[string]struct{}{ "denied2": {}, @@ -190,40 +197,40 @@ func TestNewInformerOptions(t *testing.T) { }, expectedNamespaces: []string{ - v1.NamespaceAll, + metav1.NamespaceAll, }, - expectedOptions: v1.ListOptions{ + expectedOptions: metav1.ListOptions{ FieldSelector: "metadata.namespace!=denied1,metadata.namespace!=denied2", }, }, { name: "denied namespaces with tweak", allowedNamespaces: map[string]struct{}{ - v1.NamespaceAll: {}, + metav1.NamespaceAll: {}, }, deniedNamespaces: map[string]struct{}{ "denied2": {}, "denied1": {}, }, - tweaks: func(options *v1.ListOptions) { + tweaks: func(options *metav1.ListOptions) { options.FieldSelector = "metadata.name=foo" }, expectedNamespaces: []string{ - v1.NamespaceAll, + metav1.NamespaceAll, }, - expectedOptions: v1.ListOptions{ + expectedOptions: metav1.ListOptions{ FieldSelector: "metadata.name=foo,metadata.namespace!=denied1,metadata.namespace!=denied2", }, }, } { t.Run(tc.name, func(t *testing.T) { tweaks, namespaces := newInformerOptions(tc.allowedNamespaces, tc.deniedNamespaces, tc.tweaks) - opts := v1.ListOptions{} + opts := metav1.ListOptions{} tweaks(&opts) // sort the field selector as entries are in non-deterministic order - sortFieldSelector := func(opts *v1.ListOptions) { + sortFieldSelector := func(opts *metav1.ListOptions) { fs := strings.Split(opts.FieldSelector, ",") sort.Strings(fs) opts.FieldSelector = strings.Join(fs, ",") @@ -245,3 +252,104 @@ func TestNewInformerOptions(t *testing.T) { }) } } + +// TestPartialObjectMetadataStripOnDeletedFinalStateUnknown makes sure +// that PartialObjectMetadataStrip doesn't fail on DeletedFinalStateUnknown. +func TestPartialObjectMetadataStripOnDeletedFinalStateUnknown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + // Mock the following scenario: + // 1. the informer lists the secrets and the API returns 1 item. + // 2. the informer watches the secrets resource and the API returns a watch error. + // 3. the informer lists again the secrets and the API returns no item. + // + // After the third step, the informer should send a delete event with a + // "cache.DeletedFinalStateUnknown" object. + fakeClient := fake.NewSimpleMetadataClient(fake.NewTestScheme()) + listCalls, watchCalls := &atomic.Uint64{}, &atomic.Uint64{} + fakeClient.PrependReactor("list", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) { + objects := &metav1.List{ + Items: []runtime.RawExtension{}, + } + + // The first call to list returns 1 item. Subsequent calls returns an empty list. + if listCalls.Load() == 0 { + objects.Items = []runtime.RawExtension{ + {Object: &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "777", + }, + }}, + } + } + listCalls.Add(1) + + return true, objects, nil + }) + + fakeClient.PrependWatchReactor("secrets", func(action kubetesting.Action) (handled bool, ret watch.Interface, err error) { + w := watch.NewRaceFreeFake() + + // Trigger a watch error after the first list operation. + if listCalls.Load() == 1 { + w.Error(&apierrors.NewResourceExpired("expired").ErrStatus) + } + + watchCalls.Add(1) + return true, w, nil + }) + + infs, err := NewInformersForResourceWithTransform( + NewMetadataInformerFactory( + map[string]struct{}{"bar": {}}, + map[string]struct{}{}, + fakeClient, + time.Second, + nil, + ), + appsv1.SchemeGroupVersion.WithResource("secrets"), + PartialObjectMetadataStrip, + ) + require.NoError(t, err) + + var ( + addCount = &atomic.Uint64{} + delReceived = make(chan struct{}) + ) + infs.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + t.Logf("added object %T", obj) + addCount.Add(1) + }, + DeleteFunc: func(obj interface{}) { + t.Logf("deleted object %T", obj) + close(delReceived) + }, + }) + + errCh := make(chan error, 1) + for _, inf := range infs.informers { + inf.Informer().SetWatchErrorHandler(func(r *cache.Reflector, err error) { + errCh <- err + }) + } + + go infs.Start(ctx.Done()) + + select { + case <-delReceived: + case err = <-errCh: + require.NoError(t, err) + case <-ctx.Done(): + require.FailNow(t, "timeout waiting for the delete event") + } + + // List should be called twice. + require.Equal(t, uint64(2), listCalls.Load()) + + // Watch should be called at least once. + require.GreaterOrEqual(t, watchCalls.Load(), uint64(1)) + // 1 object should have been added. + require.Equal(t, uint64(1), addCount.Load()) +} From 1c03408174e029dba0cc90db1e6b03f00f0bf3dd Mon Sep 17 00:00:00 2001 From: Simon Pasquier Date: Wed, 14 Feb 2024 11:17:59 +0100 Subject: [PATCH 2/2] update vendor/ Signed-off-by: Simon Pasquier --- .../k8s.io/client-go/metadata/fake/simple.go | 405 ++++++++++++++++++ vendor/modules.txt | 1 + 2 files changed, 406 insertions(+) create mode 100644 vendor/k8s.io/client-go/metadata/fake/simple.go diff --git a/vendor/k8s.io/client-go/metadata/fake/simple.go b/vendor/k8s.io/client-go/metadata/fake/simple.go new file mode 100644 index 0000000000..5b585f3fd6 --- /dev/null +++ b/vendor/k8s.io/client-go/metadata/fake/simple.go @@ -0,0 +1,405 @@ +/* +Copyright 2018 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 fake + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/metadata" + "k8s.io/client-go/testing" +) + +// MetadataClient assists in creating fake objects for use when testing, since metadata.Getter +// does not expose create +type MetadataClient interface { + metadata.Getter + CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) + UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) +} + +// NewTestScheme creates a unique Scheme for each test. +func NewTestScheme() *runtime.Scheme { + return runtime.NewScheme() +} + +// NewSimpleMetadataClient creates a new client that will use the provided scheme and respond with the +// provided objects when requests are made. It will track actions made to the client which can be checked +// with GetActions(). +func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeMetadataClient { + gvkFakeList := schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "List"} + if !scheme.Recognizes(gvkFakeList) { + // In order to use List with this client, you have to have the v1.List registered in your scheme, since this is a test + // type we modify the input scheme + scheme.AddKnownTypeWithName(gvkFakeList, &metav1.List{}) + } + + codecs := serializer.NewCodecFactory(scheme) + o := testing.NewObjectTracker(scheme, codecs.UniversalDeserializer()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &FakeMetadataClient{scheme: scheme, tracker: o} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// FakeMetadataClient implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type FakeMetadataClient struct { + testing.Fake + scheme *runtime.Scheme + tracker testing.ObjectTracker +} + +type metadataResourceClient struct { + client *FakeMetadataClient + namespace string + resource schema.GroupVersionResource +} + +var ( + _ metadata.Interface = &FakeMetadataClient{} + _ testing.FakeClient = &FakeMetadataClient{} +) + +func (c *FakeMetadataClient) Tracker() testing.ObjectTracker { + return c.tracker +} + +// Resource returns an interface for accessing the provided resource. +func (c *FakeMetadataClient) Resource(resource schema.GroupVersionResource) metadata.Getter { + return &metadataResourceClient{client: c, resource: resource} +} + +// Namespace returns an interface for accessing the current resource in the specified +// namespace. +func (c *metadataResourceClient) Namespace(ns string) metadata.ResourceInterface { + ret := *c + ret.namespace = ns + return &ret +} + +// CreateFake records the object creation and processes it via the reactor. +func (c *metadataResourceClient) CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + var accessor metav1.Object // avoid shadowing err + accessor, err = meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + var accessor metav1.Object // avoid shadowing err + accessor, err = meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + ret, ok := uncastRet.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("unexpected return value type %T", uncastRet) + } + return ret, err +} + +// UpdateFake records the object update and processes it via the reactor. +func (c *metadataResourceClient) UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + ret, ok := uncastRet.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("unexpected return value type %T", uncastRet) + } + return ret, err +} + +// UpdateStatus records the object status update and processes it via the reactor. +func (c *metadataResourceClient) UpdateStatus(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions) (*metav1.PartialObjectMetadata, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj) + + case len(c.namespace) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + ret, ok := uncastRet.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("unexpected return value type %T", uncastRet) + } + return ret, err +} + +// Delete records the object deletion and processes it via the reactor. +func (c *metadataResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "metadata delete fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata delete fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata delete fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "metadata delete fail"}) + } + + return err +} + +// DeleteCollection records the object collection deletion and processes it via the reactor. +func (c *metadataResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error { + var err error + switch { + case len(c.namespace) == 0: + action := testing.NewRootDeleteCollectionAction(c.resource, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"}) + + case len(c.namespace) > 0: + action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"}) + + } + + return err +} + +// Get records the object retrieval and processes it via the reactor. +func (c *metadataResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "metadata get fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata get fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"}) + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + ret, ok := uncastRet.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("unexpected return value type %T", uncastRet) + } + return ret, err +} + +// List records the object deletion and processes it via the reactor. +func (c *metadataResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) { + var obj runtime.Object + var err error + switch { + case len(c.namespace) == 0: + obj, err = c.client.Fake. + Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "metadata list fail"}) + + case len(c.namespace) > 0: + obj, err = c.client.Fake. + Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "metadata list fail"}) + + } + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + + inputList, ok := obj.(*metav1.List) + if !ok { + return nil, fmt.Errorf("incoming object is incorrect type %T", obj) + } + + list := &metav1.PartialObjectMetadataList{ + ListMeta: inputList.ListMeta, + } + for i := range inputList.Items { + item, ok := inputList.Items[i].Object.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("item %d in list %T is %T", i, inputList, inputList.Items[i].Object) + } + metadata, err := meta.Accessor(item) + if err != nil { + return nil, err + } + if label.Matches(labels.Set(metadata.GetLabels())) { + list.Items = append(list.Items, *item) + } + } + return list, nil +} + +func (c *metadataResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + switch { + case len(c.namespace) == 0: + return c.client.Fake. + InvokesWatch(testing.NewRootWatchAction(c.resource, opts)) + + case len(c.namespace) > 0: + return c.client.Fake. + InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) + + } + + panic("math broke") +} + +// Patch records the object patch and processes it via the reactor. +func (c *metadataResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "metadata patch fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "metadata patch fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"}) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + ret, ok := uncastRet.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("unexpected return value type %T", uncastRet) + } + return ret, err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b45ab8724b..f3fd44fe5d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1029,6 +1029,7 @@ k8s.io/client-go/listers/storage/v1 k8s.io/client-go/listers/storage/v1alpha1 k8s.io/client-go/listers/storage/v1beta1 k8s.io/client-go/metadata +k8s.io/client-go/metadata/fake k8s.io/client-go/metadata/metadatainformer k8s.io/client-go/metadata/metadatalister k8s.io/client-go/openapi